Handyspiel - Hochschule Fulda

Transcription

Handyspiel - Hochschule Fulda
Dokumentation
Entwicklung einer 3D-Variante des Brettspiels
Reversi fuer ein Handy
im Fach
Systemprogrammierung WS 2006/2007
von
Mehmet Akin
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Inhaltsverzeichnis
1
Einleitung ................................................................................................................................5
2
Benutzerbeschreibung .............................................................................................................6
3
2.1
Spielregeln.......................................................................................................................6
2.2
Bedienung des 3D-Menues .............................................................................................6
2.3
Spiel gegen das Handy oder einen menschlichen Spieler ...............................................7
2.4
Starten eines Netzwerkspiels...........................................................................................8
2.5
Bedienung des Spiels ......................................................................................................9
Detaildokumentation .............................................................................................................10
3.1
Klassendiagramm des Spiels.........................................................................................10
3.2
Spiellogik im Paket reversi ...........................................................................................11
3.3
Spielsteuerung im Paket main .......................................................................................11
3.4
Spielertypen im Paket player.........................................................................................12
3.5
Alpha-Beta-Algorithmus im Paket ai............................................................................13
3.6
3D-Spielemenue im Paket menu3d ...............................................................................16
3.6.1
Modellierung der Menueoptionen.........................................................................16
3.6.2
Programmierung des 3D-Menues..........................................................................17
3.6.2.1
Einlesen der 3D-Objekte ...................................................................................17
3.6.2.2
Transformation der 3D-Textobjekte..................................................................17
3.6.2.3
Setzen des Lichts...............................................................................................18
3.6.2.4
Setzen der Kamera ............................................................................................19
3.6.2.5
Rendering der 3D-Textobjekte..........................................................................19
3.7
3D-Spielbrett im Paket graphics3d ...............................................................................20
3.7.1
Modellierung der Spielsteine und anderer Objekte...............................................20
3.7.2
Programmierung der 3D-Spielelandschaft ............................................................21
3.8
3.7.2.1
Erzeugen des Spielfeldes als Gitter...................................................................21
3.7.2.2
Erstellen der Spielwiese mit einer gekachelten Textur .....................................22
3.7.2.3
Hinzufuegen von Bauemen zu der Spielelandschaft.........................................23
3.7.2.4
Setzen des Lichts und der Kamera ....................................................................23
3.7.2.6
Visualisierungen zur Anzeige eines Spielerwechsels .......................................25
Verschiedene Renderstrategien fuer Spielzuege im Paket renderer..............................25
3.8.1.1
Einfaches Ersetzen der Spielsteine....................................................................25
3.8.1.2
Fliegende Schafe zur Animation eines Zuges ...................................................25
3.8.1.3
Explosion der Schafe beim Durchfuehren eines Zuges ....................................26
Seite 2 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
3.9
Netzwerkimplementierung des Spiels...........................................................................26
3.9.1
Game Server im Paket server ................................................................................27
3.9.2
Netwerkclient im Paket network ...........................................................................27
3.9.3
Senden und Empfangen von Daten .......................................................................28
3.9.4
Kommunikationsprotokoll zwischen Client und GameServer..............................28
3.10
3.9.4.1
Login und Logout eines Spielers.......................................................................28
3.9.4.2
Einladen eines Spielers zu einem Spiel.............................................................29
3.9.4.3
Versenden von ausgefuehrten Zuegen ..............................................................30
3.9.4.4
Beenden eines Netzwerkspiels ..........................................................................30
Spielsound durch das Paket sound ................................................................................31
4
Aufgetauchte Probleme .........................................................................................................32
5
Abschliessende Bemerkung ..................................................................................................33
Anhang ..........................................................................................................................................34
Anhang A.1 Einfuehrung in die mobile 3D – Programmierung ..............................................34
Anhang A.2 Auswahl und Einsatz von Werkzeugen ...............................................................39
Anhang A.3 Portierung des Spiels auf ein reales Handy .........................................................40
Anhang A.4 Starten und Bedienen des Gameservers...............................................................41
Anhang A.5 Zeitaufwand fuer den Entwicklungsprozess........................................................41
Anhang A.6 Quellcode des Spiels............................................................................................42
Paket AI.................................................................................................................................42
BoardSimulator.java..........................................................................................................42
Intelligence.java ................................................................................................................43
IntelligenceEasy.java.........................................................................................................46
IntelligenceHard.java ........................................................................................................47
IntelligenceNormal.java ....................................................................................................51
Move.java..........................................................................................................................53
MoveGenerator.java..........................................................................................................54
Paket main .............................................................................................................................57
CanvasListener.java ..........................................................................................................57
Game.java..........................................................................................................................57
GameController.java .........................................................................................................64
Main.java...........................................................................................................................71
Paket network........................................................................................................................72
OpponentChooser.java ......................................................................................................72
PlayerConnector.java ........................................................................................................81
Seite 3 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Paket renderer........................................................................................................................89
Renderer.java.....................................................................................................................89
RendererBombSet.java......................................................................................................90
RendererFlySet.java ..........................................................................................................93
RendererNormalSet.java ...................................................................................................95
Paket sound ...........................................................................................................................95
SoundPlayer.java...............................................................................................................95
Paket graphics3d ...................................................................................................................99
Board3D.java.....................................................................................................................99
BoardGrid3D.java ...........................................................................................................112
Ground3D.java ................................................................................................................115
MessageCreator.java .......................................................................................................118
Object3DLoader.java ......................................................................................................122
Paket menu3d ......................................................................................................................124
Menu3D.java ...................................................................................................................124
Menu3DLoader.java........................................................................................................129
MenuGameLevel.java .....................................................................................................132
MenuGameStart.java.......................................................................................................135
MenuGameType.java ......................................................................................................138
Paket player .........................................................................................................................141
Player.java .......................................................................................................................141
PlayerAI.java...................................................................................................................143
PlayerHuman.java ...........................................................................................................144
PlayerNetwork.java .........................................................................................................146
Paket reversi ........................................................................................................................148
Board.java .......................................................................................................................148
Reversi.java .....................................................................................................................153
Rules.java ........................................................................................................................158
Paket server .........................................................................................................................161
GameServer.java .............................................................................................................161
ConnectionListener.java..................................................................................................167
PlayerLinker.java ............................................................................................................169
Literaturverzeichnis.....................................................................................................................178
Abbildungsverzeichnis ................................................................................................................179
Tabellenverzeichnis.....................................................................................................................179
Seite 4 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
1 Einleitung
Das Thema dieser Arbeit ist die Entwicklung einer 3D-Variante des Brettspiels Reversi (auch
bekannt als Othello) fuer mobile Plattformen, in diesem Fall fuer ein Handy. Die Arbeit entstand
im Rahmen des Pruefungsfaches Systemprogrammierung unter der Leitung von Prof. Dr.
Siegmar Gross und Prof. Dr. Werner Heinzel im Wintersemester 2006/2007 im Fachbereich
Angewandte Informatik an der Hochschule Fulda - University of Applied Sciences.
Die Anforderungen an das Spiel sind dabei die Implementierung eines 3D-Spielemenues zur
Bedienung des Spiels, die Modellierung von 3D-Modellen zur Einbindung in das Spiel sowie die
Moeglichkeit, ein Spiel zwischen zwei menschlichen Spielern auf einem einzigen Handy
durchzufuehren. Die Entwicklung soll in einem Simulator fuer das Handy auf dem Rechner
erfolgen mit dem Ziel, das entwickelte Spiel auf ein reales Handy zu portieren.
Wenn fest gestellt wird, dass im Rahmen der Bearbeitungszeit fuer dieses Projekt noch mehr
Spielefaehigkeiten realisiert werden koennen, so sollten die Basisanforderungen erweitert
werden. Die zusaetzlichen Faehigkeiten beinhalten eine KI (kuenstliche Intelligenz)Implementierung, so dass ein menschlicher Spieler alleine gegen die KI auf dem Handy spielen
kann und eine Netzwerkimplementierung, so dass ein menschlicher Spieler ortsunabhaengig
gegen andere Spieler ueber einen zentralen Spieleserver im Internet spielen kann. Weiterhin
sollte die Berechnung der KI auf einem entfernten Rechner statt finden, falls die komplexen
Berechnungen fuer die KI auf dem Handy zu langsam ausgefuehrt werden.
Um den Lesern dieser Arbeit das Verstaendnis fuer die 3D-Programmierung auf einem Handy
zu erleichtern, sollte zuerst die Einfuehrung im Anhang A.1 gelesen werden. In dieser wird die
Java Platform Micro Edition (J2ME) und das Mobile Java 3D-API nach dem Java Specification
Request JSR-184 vorgestellt, das aus dem Java Community Processes (JCP) entstanden ist.
Anhang A.2 beschreibt die benutzen Werkzeuge, um einen Ueberblick davon zu erhalten, mit
welchen Mitteln die einzelnen Teile des Spiels realisiert wurden. Danach kann die
Dokumentation gelesen werden, die mit einer Benutzerbeschreibung beginnt, um die Bedienung
des Spiels zu erklaeren. Gefolgt wird diese von einer Detaildokumentation. Diese beinhaltet eine
Beschreibung der Daten und Algorithmen, die im Spiel verwendet werden sowie ein
Klassendiagramm, um den Aufbau des Systems zu verdeutlichen. Abgeschlossen wird diese
Arbeit durch eine Beschreibung von aufgetauchten Problemen und deren Loesung. Im Anhang
befinden sich weterhin die Beschreibungen zur Installation des Spiels auf einem realen Handy,
zum Starten des Spieleservers sowie der vollstaendige kommentierte Quellcode des Programms.
Seite 5 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
2 Benutzerbeschreibung
Bevor die Bedienung des Spiels beschrieben wird, sollen zunaechst die Spielregeln dieser
Reversi-Version erlaeutert werden. Wie das Spiel auf dem Handy zum Laufen gebracht wird,
wird im Anhang A.3 beschrieben
2.1 Spielregeln
Ueblicherweise wird Reversi auf einem 8x8-Brett gespielt. Aufgrund der geringen
Anzeigenaufloesung des Handys wurde jedoch ein 6x6-Brett gewaehlt, um die Spielsteine
einigermassen gut zu erkennen.
Das Brett hat die auf dem ersten Brett in Abbildung 2.1 angezeigte Anfangsaufstellung, wobei
das w den weissen Spieler und das s den schwarzen Spieler kennzeichnet. Der weisse Spieler
beginnt. Der weisse Spieler muss nun versuchen einen schwarzen Stein zu umzingeln, um ihn in
einen weissen Stein umzuwandeln. Danach ist der schwarze Spieler dran. Falls ein Spieler
keinen Stein der gegnerischen Farbe umzingeln kann, so muss dieser aussetzen. Das Spielt endet,
wenn beide Spieler nicht mehr setzen koennen. Das passiert dann, wenn ein Spieler aussetzen
muss und der Gegner danach auch aussetzen muss und beide somit nicht mehr setzen koennen
oder wenn das Spielbrett voll ist. Umzingelt ein Spieler in mehreren Himmelsrichtugen Steine
des gegnerischen Spielers, so werden die gegnerischen Steine in allen gueltigen Richtungen
gedreht. Untere Abbildung beschreibt einen moeglichen Verlauf am Anfang des Spiels.
Abbildung 2-1: Brettstellungen bei Spielanfang
2.2 Bedienung des 3D-Menues
Die Bedienung des Spiels ist sehr leicht. Anders sollte es bei einem Handyspiel auch nicht sein.
Die Abbildung 2-2 zeigt das Spiel nach dem es ausgefuehrt wurde. Das komplette Spiel wird nur
ueber die rot gekennzeichneten Tasten plus den Pfeiltasten bedient, so wie es auch eigentlich bei
allen anderen Handyspielen der Fall ist.
Seite 6 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Abbildung 2-2: Steuerung des Spiels
Nachdem die Nachricht Mobile Reversi 3D angezeigt wurde, erscheint das Startmenue, dass aus
den beiden Optionen Start und Exit besteht. Das folgende Ablaufdiagramm in Abbildung 2-3 soll
deutlich machen, welche weiteren Untermenues ueber das Startmenue zu erreichen sind und
welche Spieltypen gestartet werden koennen. Wie man ein Netzwerkspiel gegen einen entfernten
Spieler startet, wird im naechsten Abschnitt gesondert behandelt.
2.3 Spiel gegen das Handy oder einen menschlichen Spieler
Abbildung 2-3: Startmenue und Untermenues des Spiels
Befindet man sich im Startmenue mit den beiden Optionen Start und Exit, so kann eine andere
Option gewaehlt werden, indem man die Hoch- und Runter(-pfeil-)tasten betaetigt. Die jeweils
ausgewaehlte Option ist durch ein dunkleres Orange und das Rotieren der entsprechenden
Seite 7 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Option deutlich zu erkennen. Durch Druecken der Feuertaste wird die entsprechende Option
gewaehlt. Durch Waehlen der Option Exit gelangt man wieder ins Handyhauptmenue zurueck.
Durch Wahl von der Option Start gelangt man zum Untermenue, in dem der Spieltyp gewaehlt
wird. In der obigen Abbildung wurde die Option 1P vs Com gewaehlt, was ein Spiel gegen das
Handy bedeutet. Es kann zwischen drei Schwierigkeitsstufen gewaehlt werden. Nach Wahl der
Schwierigkeitsstufe wird dann das Spiel gestartet. Ein Spiel zwischen zwei menschlichen
Spielern kann entsprechend durch Wahl der Spielart 1P vs 2P direkt gestartet werden.
In einem Untermenue ist es immer moeglich, durch Druecken der Returntaste in das Menue zu
wechseln von dem es aufgerufen wurde.
2.4 Starten eines Netzwerkspiels
Abbildung 2-4: Fenster zum Starten eines Netzwekspiels
Waehlt man die Option 1P vs Net als Spieltyp, so erscheint zunaechst ein Einwahlfenster, ueber
den man sich an einem Spieleserver im Internet anmelden kann (erstes Fenster Abbildung 2-4).
Ein fortlaufender Text („Enter your name and then push Login!“) im oberen Menueteil
beschreibt fuer diejenigen, fuer die es nicht einleuchtend ist, was zu tun ist. Falls man keinen
Namen angibt, der Server nicht erreichbar ist oder der Name, der gewaehlt wurde schon von
einem anderen Spieler, der eingewaehlt ist, benutzt wird, so wird das entsprechend im
fortlaufenden Text in der obersten Zeile im Menue mit den Nachrichten „You must enter at least
one character!“, „Server unreachable, please ensure right server address and port!“ oder
„Your name is already used! Please choose another one!” kenntlich gemacht.
Durch Druecken der Ok-Taste, die sich unter dem Text „Login“ auf der Anzeige befindet, kann
man die Einwahl starten. Hat man sich erfolgreich eingewaehlt, so erscheint eine Liste mit allen
Spielern, die verfuegbar sind und nicht gerade selber spielen (Fenster 2 Abbildung 2-4). Durch
Druecken der Zurueck-Taste, die sich unter dem Text „Back“ befindet, kann jederzeit zu dem
Untermenue zurueckgesprungen werden, in dem man den Spieltyp auswaehlt. Ist kein Spieler
eingewaehlt, so wird das durch die Nachricht „No players online!“ kenntlich gemacht. Werden
verfuegbare Spieler aufgelistet, so kann man mit den Hoch- und Runtertasten den Spieler
Seite 8 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
auswaehlen, gegen den man spielen moechte oder man wartet darauf, dass man selber eingeladen
wird. Einen Spieler kann man zu einem Spiel einladen, indem man die Ok-Taste drueckt, ueber
dem der Text „Connect“ steht. Der eingeladene Spieler erhaelt dann eine Einladung (Fenster 3
und 4 Abbildung 2-4). Bestaetigt der eingeladene Spieler mit der Ok-Taste, startet das Spiel auf
beiden Seiten. Lehnt er ab, so wird in der oberen Textzeile des Einladenden angezeigt, dass der
Spieler das Spiel abgelehnt hat (Fenster 5 Abbildung 2-4) und es wird wieder die Spielerliste mit
verfuegbaren Spielern angezeigt, so dass der Spieler einen anderen einladen kann. Kommt es
vor, dass der eingeladene Spieler die Einladung nicht wahrnimmt und somit nicht auf die
Anfrage reagiert, so erhaelt der Einladende ein Timeout-Signal nach 20 Sekunden, damit er nicht
lange warten muss und es wird wieder die Spielerliste angezeigt. Auch das eingeladene Handy
erhaelt das Timeout-Signal, was bewirkt, dass das Einladungsfenster wieder verschwindet. In der
oberen Textzeile steht jedoch, dass derjenige Spieler zu einem Spiel eingeladen wurde, aber
nicht anwesend war und es wird wieder die Spielerliste angezeigt.
Jegliche Formen von Netzwerkverbindungsunterbrechungen, seien es durch das Beenden des
Servers oder durch ein vorzeitiges Beenden eines Spiels durch einen Spieler, fuehren dazu, dass
das Spiel auf beiden Spielerseiten zurueck zum Startmenue kehrt.
(Anmerkung : Die dargestellten Menuabbildungen fuer ein Netzwerkspiel sind Schnappschuesse
vom Simulator und stellen grafisch keine 1:1 Beziehung dar. Auf dem realen Handy sieht das
Menue grafisch sehr viel ansprechender aus.)
2.5 Bedienung des Spiels
Abbildung 2-5: 3D-Spielelandschaft
Nachdem der Spieler ein Spiel gestartet hat, verschwindet das Menue und es erscheint das
Spielfeld im Vollbildmodus. Der gruene Pfeil neben dem weissen Schafskopf macht klar, welche
Farbe an der Reihe ist (Abbildung 2-5). Die weisse Farbe beginnt immer das Spiel, egal welche
Spielart gespielt wird. Die Spielsteuerung ist in jeder Spielart dieselbe, nur die Nachrichten am
Spielanfang, die anzeigen, wer den allerersten Zug macht, sind verschieden. Startet der Spieler
Seite 9 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
ein Spiel gegen das Handy, so erscheint am Spielanfang kurz die Meldung „You start!“ um zu
verdeutlichen, dass der menschliche Spieler beginnt. Wird ein Spiel zwischen zwei menschlichen
Spielern gestartet, so erscheint die Nachricht „White begins!“. Bei einem Netzwerkspiel
erscheint bei demjenigen, der die Einladung geschickt hat die Nachricht „You start!“, bei dem,
der eingeladen ist „Opponent starts!“. Ein Zug kann durchgefuehrt werden, indem man das
gelbe Viereck (Plane) mit den Pfeiltasten hoch, runter, nach rechts oder nach links bewegt und
dann die Feuertaste drueckt, um zu signalisieren , dass der Spieler an die gewaehlte Position
setzen will. Macht ein Spieler einen Zug und der Gegenspieler muss aussetzen, so wechselt der
gruene Pfeil einfach seine Position nicht und es ist klar, dass der Spieler noch einmal an der
Reihe ist.. Wird die Return-Taste gedrueckt, so wird das Spiel beendet und das Startmenue
erscheint.
3 Detaildokumentation
3.1 Klassendiagramm des Spiels
Abbildung 3-1: Klassendiagramm des Spiels
Das dargestellte Klassendiagramm beinhaltet alle Klassen des Spiels ohne den Spieleserver, der
aus drei Klassen besteht. Der Server wird in Kapitel 3.9.1 genauer beschrieben. Es wurde
Seite 10 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
versucht, eine moeglichst lose Kopplung zwischen den Paketen und eine enge Kohaesion in den
Paketen zu erreichen.
Im Folgenden werden die speziell in den einzelnen Paketen benutzten Datenstrukturen und
Algorithmen (spezielle Variablen, Konstanten, Tabellen, Arbeitsbereiche, Dateien, usw.) naeher
erlaeutert. Fuer das genaue Verstaendnis der Klassen und einzelnen Funktionen sei auf den
Quellcode im Anhang A.6 verwiesen.
3.2 Spiellogik im Paket reversi
Das Paket reversi besteht aus den drei Klassen Reversi, Rules und Board. Rules ist eine
Implementierung der in Kapitel 2.1 genannten Spielregeln. Board ist die interne Darstellung des
Spielbretts, auf dem alle Berechnungen fuer einen gueltigen Spielzug statt finden. Es enthaelt
weiterhin alle Konstanten, die waehrend eines Spiels oft gebraucht werden. Diese sind
WHITE_PLAYER, BLACK_PLAYER und EMPTY, die durch einen byte-Wert jeweils angeben,
mit was fuer einer Steinfarbe ein Feld besetzt ist oder ob es noch nicht besetzt ist, die 2dimensionale byte-Konstante DIRECTIONS, in der die acht Himmelsrichtungen, in denen Steine
gedreht werden koennen, repraesentiert werden sowie die byte-Werte MAX_X und MAX_Y die
die Grenzen des Spielbretts angeben. Die Klasse Reversi verbindet Board und Rules, sodass
andere Pakete nur Zugriff auf Reversi haben muessen.
3.3 Spielsteuerung im Paket main
Das Paket main besteht aus den Klassen Game, GameController und der Schnittstelle
CanvasListener. Die Schnittstelle CanvasListener wird von den Klassen Game und Klassen, die
von menu3d.Menu3D abgeleitet sind, implementiert. Der GameController dient zum Starten
eines Spiels sowie zur Steuerung des 3D-Menues. Es kapselt die Benutzerinteraktion (Druecken
der einzelnen Tasten) des menschlichen Spielers mit dem Handy.
Game kapselt ein Spiel und wird von GameController gestartet. Es entscheidet welcher Spieler
gerade an der Reihe ist, prueft, ob ein Spielzug eines Spielers gueltig ist und veranlasst je nach
Gueltigkeit das wirkliche Setzen eines Spielsteins, entscheidet ob ein Spieler aussetzen muss und
evaluiert das Spiel, wenn es zu Ende ist. Fuer all diese Entscheidungen benutzt Game die Klasse
Reversi.
Das Spiel laueft nicht wie in fast allen Spielen in einem eigenen synchronen Thread ab. Um die
Zeit, die ausser fuer die 3D-Berechnungen des Bretts auf der Handyanzeige fuer andere im
Hintergrund laufende Threads gebraucht wird, so gering wie moeglich zu halten, damit der
Seite 11 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
groesste Teil der Prozessorleistung auf die 3D-Berechnungen zum Anzeigen von Spielzuegen
und des 3D-Spielbretts ausgenutzt werden kann, wurde folgende asynchrone Loesung benutzt:
Das gesamte Spiel wird alleine durch die Tastenbetaetigungen des Spielers asynchron gesteuert.
Der GameController besitzt dafuer die Variable CanvasListener displayable3D, und leitet somit
die Auswertung des Wertes der Taste, die gedrueckt wurde ueber die Funktion
void
reactOnKeyPressed (int gameAction) von CanvasListener an das Objekt weiter, das
displayable3D referenziert. Zu Programmbeginn referenziert es ein Menu3D-Objekt, dass durch
entsprechende Tastenbetaetigungen die Auswahl der Menue-Optionen ansteuert, bei Starten
eines Spiels ein Game-Objekt, dass entsprechend die Tastenbetaetigung auswertet, um ein Feld
auf dem Spielbrett auszuwaehlen und einen Zug zu machen. Durch Benutzung einer Schnittstelle
werden die Tastenwerte somit transparent uebergeben ohne dass der GameController selber
wissen muss, was gerade angezeigt wird.
Die Klasse Main (nicht im Klassendiagramm), die die Klasse javax.microedition.midlet.MIDlet
erweitert, dient nur als Programmeinstieg und ueber gibt eine Referenz auf die Handyanzeige,
auf der alle grafischen Darstellungen statt finden, beim Erzeugen eines GameControllerObjektes an dieses Objekt.
3.4 Spielertypen im Paket player
Das Paket Player besteht aus der abstrakten Klasse Player sowie den daraus abgeleiteten Klassen
PlayerAI, PlayerHuman, PlayerNetwork. Diese einzelnen Spielertypen ueberschreiben die von
Player
geerbten
abstrakten
Methoden
doNextSet
(),
handlePlayerChanged
()
und
notifyHasToStay (), die jeweils von der Klasse Game waehrend eines Spiels je nach
Spielsituation aufgerufen werden. Ein menschlicher Spieler PlayerHuman macht einen Zug,
indem er mit den Pfeiltasten des Handys das entsprechende Feld auswaehlt, auf das er setzen
will und dann mit der Feuertaste bestaetigt. Ein PlayerAI, der eine kuenstliche Intelligenz
kapselt, setzt einen Stein, indem ein fuer die gewaehlte Schwierigkeitsstufe angepasster Zug
berechnet und gesetzt wird. Ein PlayerNetwork dient dazu, den Zug, den der Gegenspieler
entfernt ausgefuehrt hat, auch lokal auszufuehren und somit den Spielverlauf zwischen den
beiden entfernten menschlichen Spielern zu synchronisieren.
Seite 12 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
3.5 Alpha-Beta-Algorithmus im Paket ai
Das Paket ai besteht aus den sieben Klassen BoardSimulator, Move, MoveGenerator, der
abstrakten Klasse Intelligence und den davon abgeleiteten Klassen IntelligenceEasy,
IntelligenceNormal, IntelligenceHard.
Move stellt einen einzigen Zug mit den entsprechenden Koordinaten dar. Der MoveGenerator
berechnet alle Zuege, die in einer bestimmten Spielsituation fuer eine bestimmte Spielerfarbe
moeglich sind. Die Klasse Intelligence wird von PlayerAI benutzt und stellt eine Basisklasse dar,
dessen abgeleitete Klassen die abstrakte Methode void searchForBestPosition (byte [][] board,
byte color) ueberschreiben muessen, die den fuer die jeweilige Spielsituation und an die
Schwierigkeitsstufe der kuenstlichen Intelligenz angepassten Zug berechnet.
In der einfachsten Schwierigkeitsstufe mit IntelligenceEasy wird irgendein Zug (ermittelt durch
einen Zufallszahlengenerator) von den moeglichen Zuegen fuer eine bestimmte Spielsituation,
die von MoveGenerator geliefert wird, gesetzt.
Durch einen Zug koennen entweder nur ein oder mehrere gegnerische Steine gedreht werden.
Ein Spieler will natuerlich immer so viele Steine wie moeglich mit einem Zug drehen.
Zusaetzlich kommt hinzu, dass zum Beispiel durch Setzen eines Steines an den Randpositionen
die Wahrscheinlichkeit verringert wird, dass dieser Stein im Laufe des Spiels vom Gegner
gedreht wird. Somit ergibt sich je nach Feldposition eine bestimmte Bewertung fuer dasjenige
Feld, auf das gesetzt werden kann. Unten stehende Abbildung 3-2 zeigt die fuer das Spiel
verwendete individuelle Bewertungsmatrix in Form eines 2-dimensionalen byte-Arrays:
Abbildung 3-2: Bewertungsmatrix des Spielbretts
Ein Stein in den vier Ecken kann waehrend des Spielverlaufs nicht gedreht werden und erhaelt
daher den hoechsten Wert von 1000. Den niedrigsten Wert von 5 erhalten die vier Felder, die
Seite 13 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
von allen acht Seiten gedreht werden koennten und ueber den es moeglich ist, dass der Gegner
einen Stein in den Ecken setzen kann. Die Felder, die sich an den Raendern des Spielfeldes
befinden und ueber den der Gegner einen Stein in die Ecke setzen kann, erhalten einen etwas
hoeheren Wert mit 10, da der Gegner nur zwei Moeglichkeiten hat, diese Steine zu drehen. Einen
Wert von 300 bekommen die Felder am Rand des Spielfeldes, die der Gegner somit nur aus zwei
moeglichen Position schlagen kann. Jedoch kann der vom Gegner gesetzte Stein neben einem
300er-Feld niemals in eine Eckposition gelangen, kann aber dazu fuehren, dass der Gegner den
Stein auf ein 10er-Feld setzt und somit dem Spieler ermoeglicht, danach die Chance zum Setzen
auf ein 1000er-Feld zu erlangen. Die anderen Werte ergeben sich analog aus der Ueberlegung
heraus, dass eine Position einen niedrigeren Wert erhaelt falls der gegnerische Spieler nach dem
eigenen Setzen auf diese Position eine Position mit einer hoeheren Bewertung erlangen kann und
umgekehrt.
Zusaetzlich zu der Bewertungsmatrix gibt es die Bewertungsfunktion, die aus der jeweiligen
Brettbesetzung einen einzigen Wert fuer die jeweilige Spielerfarbe berechnet, der dazu dient, den
jeweiligen Nutzen eines Spielzugs fuer eine bestimmte Farbe zu definieren. Ein Spielfeld, dass
mit einem weissen Stein besetzt ist, bekommt den Wert 1, ein schwarzer Stein den Wert –1 und
ein leeres Feld den Wert 0. Die Bewertungsfunktion berechnet eine Summe der Produkte
Feldbewertung * Feldfarbe ueber jedes einzelne Feld des Spielbretts. Ist die Summe positiv, so
bedeutet es, dass der weisse Spieler zu der jeweiligen Spielsituation, in dem die
Bewertungsfunktion aufgerufen wird im Vorteil ist. Bei einem negativen Wert ist der schwarze
Spieler im Vorteil und bei einem Wert von 0 ist das Spiel ausgeglichen. Die Bewertungsmatrix
und Bewertungsfunktion wird von der Klasse Intelligence bereit gestellt.
IntelligenceNormal benutzt die Bewertungsfunktion und sucht sich den Zug aus den moeglichen
Zuegen heraus, fuer den die Brettbewertung nach dem Setzen die beste ist. Haben mehrere
Zuege die gleiche beste Bewertung fuer die jeweilige Farbe, so wird zufaellig einer daraus
gewaehlt, was auch dazu fuehrt, dass das Spiel abwechslungsreich bleibt.
IntelligenceNormal betrachtet nur die Brettbewertung nachdem ein einziger Zug gemacht wurde.
Das bedeutet, dass IntelligenceNormal weiss, dass die Spielsituation nach dem Setzen seines
Steins fuer ihn vorteilhafter ist, aber nicht, dass es immer noch so ist, wenn er nach dem
gegnerischen Setzen wieder an der Reihe ist. Es denkt somit nicht weiter.
An diesem Punkt setzt die Klasse IntelligenceHard an. IntelligenceHard benutzt eine an dieses
Reversi-Spiel angepasste Version des Alpha-Beta Algorithmus, um den besten Zug zu waehlen,
der ihm verspricht, dass auch nach einer bestimmten Tiefe (z.B. 3 Zuege im Voraus) die
Spielsituation fuer ihn vorteilhafter ist wie fuer den Gegner.
Seite 14 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Der Alpha-Beta-Algorithmus ist ein Algorithmus zur Bestimmung des besten Spielzuges bei
Spielen, bei denen zwei Spieler abwechselnd Zuege ausfuehren muessen. Beim rekursiven
Aufbau des Baumes werden jeweils zwei Werte Alpha und Beta aktualisiert, ueber die es
moeglich ist zu entscheiden, ob ein Teilbaum nicht mehr berechnet werden muss, weil er fuer
das Endergebnis keine Rolle spielt. Die Grundidee des Algorithmus besteht darin, dass der
weisse Spieler versucht, den Wert, den er bei optimaler Spielweise des schwarzen Spielers
mindestens erreichen kann, zu maximieren und umgekehrt. Der Alpha-Wert, den der weisse
Spieler versucht zu maximieren, wird mit –inf (minus unendlich) und der Beta-Wert, den der
schwarze Spieler versucht zu minimieren, wird mit +inf (unendlich) initialisiert [14].
Wenn ein Knoten, indem maximiert wird einen Folgezug besitzt, dessen Wert groesser als der
Beta-Wert ist, so wird die Suche fuer diesen Knoten abgebrochen (Beta-Cutoff), weil das
Ergebnis ein zu gutes Ergebnis fuer den gegnerischen Spieler liefern wuerde. Ist der Wert des
Zuges kleiner als Beta und groesser als Alpha, so wird dieser Zug nach oben weiter gereicht.
Umgekehrt gilt fuer den minimierenden Spieler, dass bei einem Wert abgebrochen wird, der
kleiner als Alpha ist (Alpha-Cutoff) und Beta an den Wert des Zuges angepasst wird, falls dieser
groesser als Alpha und kleiner als Beta ist.
Abbildung 3-3: Alpha-Beta Algorithmus bei einer Tiefe von 3
Quelle: http://de.wikipedia.org/wiki/Alpha-Beta-Pruning
Die obige Abbildung 3-3 zeigt einen Baum, der bei Anwendung des Algorithmus bei einer
Suchtiefe von 3 aufgespannt werden kann. Die Zahlen an den Blaettern 1-18 geben die jeweilige
Brettbewertung zur jeweiligen Spielsituation nach 3 Zuegen an. Der linke Wert eines Knotens
stellt den Alpha-Wert, der mittlere Wert die jeweilige Bewertung fuer den Knoten und der rechte
Wert den Beta-Wert dar. Der Wertebereich zwischen der unteren Grenze Alpha und der oberen
Seite 15 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Grenze Beta wird Fenster genannt. Wie man in der Abbildung erkennt, wird immer mit einem
maximalen Fenster begonnen.
Der allererste Teilbaum (Pfad zu Blatt 1) wird immer komplett ausgewertet, um eine
Ausgangsbasis fuer die weitere Suche zu haben. In diesem Beispiel hat der beste Nachfolgezug
fuer den maximierenden Knoten ganz links unten einen Wert von 10, der an den minimierenden
Vaterknoten nach oben gereicht wird. Der minimierende Vaterknoten setzt das neue
Fensterintervall auf (-inf, 10] und uebergibt es seinem naechsten maximierenden Kindknoten.
Dieser hat jedoch einen Zug mit dem Wert 12, der den Beta-Wert uebersteigt, und somit kann
die Suche fuer diesen Teilbaum abgebrochen werden, weil der minimierende Spieler diesen
Teilbaum nie auswaehlen wuerde, weil er ein besseres Ergebnis fuer den maximierenden Spieler
liefern wuerde als der Teilbaum ganz links unten mit dem Wert 10, was der minimierende
Spieler versucht zu verhindern. Analog verhalten sich die beiden anderen Cutoffs, die mit roten
Zahlen gekennzeichnet sind.
3.6 3D-Spielemenue im Paket menu3d
Das Paket menu3d besteht aus den Klassen Menu3DLoader, der abtstrakten Klasse Menu3D
sowie den davon abgeleiteten Klassen MenuGameStart, MenuGameType und MenuGameLevel.
MenuGameStart stellt das Startmenue zu Programmbeginn dar, aus dem die weiteren
Untermenues zu erreichen sind, wie dies in den Abbildungen der Benutzerbeschreibung gezeigt
wurde. Menu3DLoader liefert den Menues die einzelnen 3D-Textobjekte, aus denen sie
aufgebaut werden.
3.6.1 Modellierung der Menueoptionen
Die einzelnen Menueoptionen wie Start und Exit im Startmenue, 1P vs Com, 1P vs 2P und 1P vs
Net im Spieltypauswahlmenue und Easy, Normal und Hard im Menue zum Waehlen der
Schwierigkeitsstufe gegen die KI wurden ueber ein Textobjekt mit der Schriftart Trebuchet MS
Fett in Blender3D modelliert. Ein Textobjekt in Blender3D ist zunaechst nur ein 2D-Objekt. Um
es 3-dimensional wirken zu lassen, muessen die Parameter Extrude und BevelDepth, die sich im
Feld Curve and Surface im unteren Menueteil von Blender befinden, gesetzt werden. Zuerst wird
Extrude benutzt, um den 2D-Text in die Tiefe zu extrudieren. Danach koennen die Kanten des
nun in 3D vorliegenden Textes mit Einstellen des BevelDepth- und BevResol (Bevel Resolution)
-Parameters abgerundet werden. Nachdem das Objekt fertig modelliert ist, kann dem Objekt ein
Seite 16 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Material hinzugefuegt werden, um die Farbe, die vom Licht reflektiert wird zu aendern. Bei
allen 3D-Menueoptionen wurde die Farbe Orange gewaehlt [11].
Abbildung 3-4: Textobjekt modelliert mit Blender3D
Die obige Abbildung 3-4 verdeutlicht die Bedeutung von Extrude und Bevel. Die Modelle sind
parallell zur x-y-Ebene und wurden im Ursprung des Koordinatensystems platziert.
3.6.2 Programmierung des 3D-Menues
Ein Menue besteht aus den entsprechenden 3D-Textobjekten, die die Optionen dar stellen, die
ausgewaehlt werden koennen sowie einem Hintergrundbild. Die 3D-Textobjekte werden ueber
den Menu3DLoader geladen. Da sich die Objekte im Ursprung des Koordinatensystems
befinden, werden Sie zuerst an die entsprechenden Position auf der Anzeige bewegt. Eine
Option, die aktuell gewaehlt ist, wird durch entsprechendes Rotieren des 3D-Textobjektes
kenntlich gemacht. Um die Objekte zu beleuchten wird ein gerichteter Lichtstrahl benutzt
(Directional Light). Da die Text-Objekte parallel zur x-y-Ebene sind, wird die Kamera entlang
der negativen z-Achse mit Blick auf die x-y-Ebene platziert, um die Objekte sehen zu koennen.
3.6.2.1 Einlesen der 3D-Objekte
Das Einlesen der 3D-Textobjekte uebernimmt die Klasse Menu3DLoader. Die Optionen als 3DTextobjekte werden je Menueart in einer separaten M3G-Datei gespeichert. Somit sind es drei
Dateien, eins fuer das Startmenue, eins fuer das Spieltypauswahlmenue und eins fuer das Menue
fuer die Auswahl der Schwierigkeitsstufe bei einem Spiel gegen das Handy. In jeder Datei wird
auch zusaetzlich eine Kamera gesetzt, da das Setzen der Kamera in Blender3D sehr viel
schneller geht als selber eine Kamera programmatisch zu erzeugen und an die entsprechende
Position zu verschieben. Das Laden des Szenegraphen in der M3G-Datei ueber die Klasse
Loader sowie das Finden der einzelnen 3D-Textobjekte im Szenegraphen wird in der gleichen
Weise durchgefuehrt wie es in der Einleitung in Anhang A.1 erklaert ist.
3.6.2.2 Transformation der 3D-Textobjekte
Nachdem der Szenegraph eingelesen wurde, befinden sich die Objekte im Ursprung und
muessen an die entsprechenden Position verschoben werden. Fuer das Startmenue bedeutet das
Seite 17 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
zum Beispiel, dass das Textobjekt fuer Start nach oben entlang der y-Achse und das Textobjekt
fuer Exit nach unten entlang der y-Achse verschoben werden muss, damit sich beide Texte nicht
ueberlappen. Ein 3D-Objekt kann verschoben werden, indem seine Transformationsmatrix
manipuliert wird. Standardmaessig ist die Transformationsmatrix eines 3D-Objektes die
Identitaetsmatrix, die keine Auswirkungen auf das 3D-Objekt hat. Um das Objekt jedoch zu
verschieben, muss man nun dem Objekt eine neue Transformationsmatrix zuweisen. Dafuer wird
eine neue Transformationsmatrix mit new javax.microediton.m3g.Transform () erzeugt, die mit
der Identitaetsmatrix initialisiert wird. Eine Translation der Werte der Matrix wird mit der
Methode void postTranslate (float tx, float ty, float tz) erreicht. Mit setTransform (Transform t)
wird dann die aktuelle Matrix des 3D-Textobjektes auf die neue Translationsmatrix gesetzt. Alle
Matrizen, die in der Mobile Java3D-API benutzt werden, sind 4x4 Fliesskomma-Matrizen.
Zu beachten ist das Wort „post“ in postTranslate. Das bedeutet, dass die Translation an das
Ende des Transformationsstapels gehaengt wird und somit beim Ausfuehren als erste Anweisung
ausgefuehrt wird, wenn keine anderen Transformationen im Stapel ueber dieser Transformation
liegen (Stapel -> LIFO, Last In First Out). Die Rotation der aktuell ausgewaehlten Option wird
beim Rendering direkt ueber eine Rotationsmatrix erreicht und wird unter Kapitel 3.6.2.5
behandelt.
3.6.2.3 Setzen des Lichts
Das Mobile Java3D-Api bietet vier Arten von Licht:
Ambientes Licht - Alle Objekte in der Szene werden von allen Richtungen mit einer
Lichtintensitaet beleuchtet, die ueberall in der Szene die gleiche Staerke hat. Die Position und
Richtung der Lichtquelle ist somit irrelevant.
Direktionales Licht - Entspricht dem Sonnenlicht der realen Welt. Alle Objekte werden von nur
einer Richtung mit einer konstanten Intensitaet beleuchtet. Die Lichtstrahlen verlaufen entlang
der negativen z-Achse des lokalen Koordinatensystem des Lichts. Wie beim ambienten Licht ist
es auch hier egal, wo die Lichtquelle positioniert wird.
Omnidirektionales Licht (auch Punktlicht) - Vom Ursprung einer Lichtquelle wird in alle
Richtungen Licht mit der gleichen Intensitaet ausgestrahlt, dass ueber die Entfernung hin
gedaempft wird. Die Richtung spielt somit keine Rolle bei diesem Licht, jedoch die Position, an
dem die Lichtquelle positioniert wird.
Spot (Scheinwerfer) Licht - Dieses Licht entspricht dem Prinzip einer Tischlampe. Von einer
Lichtquelle ausgehend wird Licht nur in bestimmte, durch einen Winkel fest gelegte Richtungen
Seite 18 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
ausgestrahlt, dessen Intensitaet mit zunehmender Entfernung wie beim omnidirektionalen Licht
gedaempft wird. Somit spielt die Orientation sowie die Position der Lichtquelle eine Rolle [1].
Die Berechnungskomplexitaet fuer die einzelnen Lichter nimmt in Reihe der Aufzaehlung zu.
Fuer dieses Spiel wurde immer nur ein direktionales Licht benutzt, da es die Objekte grafisch
ansprechender aussehen laesst im Vergleich zum faden ambienten Licht und ein fluessiges
Spielen erlaubt im Gegensatz zum omnidirektionalen und Spot-Licht, wobei das unterschiedliche
Beleuchten von den Spielsteinen und Abnehmen der Lichtintensitaet bei zunehmender
Entfernung fuer dieses Brettspiel ohnehin nicht sinnvoll scheint.
Lichter werden mit der Klasse Light erzeugt. Ein Light-Objekt wird automatisch mit
direktionalem Licht initialisiert und die Lichtstrahlen verlaufen parallel in Richtung der
negativen z-Achse. Wenn der Lichtmodus explizit noch mal gesetzt werden soll, so kann man
das mit void setMode (Light.DIRECTIONAL) erreichen.
3.6.2.4 Setzen der Kamera
Die Kamera fuer das 3D-Menu wurde in jeder M3G-Datei fuer die verschiedenen Menueteile im
3D-Modellierer gesetzt. Der Projektionsmodus der Kamera wird auch mit exportiert. Bei dieser
Kamera, der fuer das 3D-Menue benutzt wird, wird Perspektivprojektion benutzt, um einen
besseren 3D-Effekt zu erzielen.
3.6.2.5 Rendering der 3D-Textobjekte
Wie in der Einleitung im Anhang A.1 schon beschrieben wurde, ist Immediate Mode Rendering
der effektivere Modus 3D-Objekte zu rendern. Jedoch ist hier ueber eine Strategie
nachzudenken, wann und wie das Hintergrundbild gebildet wird, um den Hintergrund mit diesem
zu loeschen und nur das Objekt zu rendern, dass transformiert wurde.
Das Rendering des Startmenues und allen anderen Untermenues erfolgt nach folgendem Schema:
Nur das 3D-Textobjekt wird gerendert, dass die ausgewaehlte Option auf dem Menue dar stellt
und in einer Endlosschleife solange rotiert bis zu einer anderen Option auf dem gleichen Menue
oder in ein anderes Untermenue gewechselt wird. Das alleinige Rendering dieses Objektes
wuerde jedoch dazu fuehren, dass andere Menueoptionen nicht auf der Anzeige dargestellt
werden. Daher muss der Hintergrund mit einem geeigneten Bild geloescht werden bevor das 3DTextobjekt auf der Anzeige gerendert wird. Das Bild muss aus allen anderen 3D-Textoptionen
ausser der aktuell ausgewaehlten bestehen. Das Hintergrundbild kann man dadurch erzeugen,
indem man das Textobjekt (die Option), das man auswaehlt vom Szenegraphen entfernt und den
restlichen Szenegraphen, der alle Objekte ausser dem ausgewaehlten enthaelt in ein Bild rendert.
Seite 19 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Logischerweise muss man das Textobjekt, was zuvor ausgewaehlt wurde, wieder an den
Szenegraphen hinzu fuegen, da dieser beim Auswaehlen auch zuerst entfernt wurde.
Das Rendering des Szenegraphen in das Bild erfolgt im Retained Mode Rendering. Da das
entsprechende Hintergrundbild jedoch nur erzeugt wird, sobald der Benutzer eine andere Option
im Menue auswaehlt, ist diese Strategie sehr viel schneller als die gesamte Anzeige waehrend
der Rotation der ausgewaehlten Option zu rendern. Das Rendering der Option, die rotieren soll,
erfolgt nach Immediate Mode Rendering, indem die Rotationsmatrix der render-Funktion
uebergeben wird. Das fuehrt dazu, dass die Matrix des Objektes mit der Rotationsmatrix
multipliziert wird. Da die Matrix des Objektes, das zu einer Translation fuehrt bei Initialisierung
des Objektes gesetzt wurde, rotiert das Objekt daher zuerst am Ursprung und wird danach an
seine richtige Position verschoben, da der Matrix-Stapel des Objektes nach dem LIFO(Last In
First Out)-Prinzip abgearbeitet wird. Anzumerken ist, dass durch
Uebergabe der
Rotationsmatrix an die render-Funktion das Objekt rotiert wird, aber seine interne Matrix (, die
zu einer Translation fuehrt), nicht manipuliert wird. Das hat den Vorteil, dass die Matrix eines
ausgewaehlten Objektes nicht jedes Mal neu gesetzt werden muss, wenn zu einer anderen Option
gewechselt wird. (Siehe dazu Anhang A.6 Quellcode in der Klasse menu3d.Menu3D)
3.7 3D-Spielbrett im Paket graphics3d
Das Paket graphics3d besteht aus den Klassen Board3D, BoardGrid3D, Ground3D,
MessageCreator und dem Object3DLoader. Board3D stellt das interne Spielbrett, dass im Paket
reversi durch die Klasse Board gebildet wird, in 3D auf der Anzeige dar. BoardGrid3D stellt das
6x6-Spielfeld als Gitter auf einer Wiese in der Landschaft dar, dass durch Ground3D gebildet
wird. Der MessageCreator dient zum Anzeigen von Nachrichten bei Spielanfang und -ende. Der
Object3DLoader dient zum Laden der fuer das 3D-Spielfeld benoetigten Modelle.
3.7.1 Modellierung der Spielsteine und anderer Objekte
Fuer dieses Spiel wurden die Spielsteine als ein weisses Schaf fuer den weissen Spieler und ein
schwarzes Schaf fuer den schwarzen Spieler modelliert. Weiterhin wurde ein gelbes Plane
(Quadrat) modelliert, dass zum Anzeigen des aktuell gewaehlten Spielfeldes benutzt wird und
ein Bombenzuender der zusammen mit Rauch, der auf dem Feld eines Schafes den grafischen
Effekt erzielt, dass das Schaf explodiert. Alle vier Modelle befindet sich in einer M3G-Datei, das
eingelesen wird. Das Schaf ist eine Zusammensetzung aus UVSphere-Objekten, die Kugeln in
Blender3D-darstellen. Diese wurden je nachdem, welchen Koerperteil des Schafes sie bilden
Seite 20 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
sollen in die entsprechenden Positionen verschoben , skaliert und rotiert. Die Materialfarben der
einzelnen Koerperteile wurden danach entweder auf weiss oder schwarz eingestellt, so dass die
Schafe zu unterscheiden und dem jeweiligen Spielertyp zuzuordnen sind. Es wurde je ein
Exemplar eines schwarzen Schafes und eines weissen Schafes modelliert und im Ursprung des
Koordinatensystems platziert.
Abbildung 3-5: Modelle, modelliert mit Blender3D
Das Viereck, dass zur Kennzeichung des aktuell ausgewaehlten Spielfeldes benutzt wird, wird in
Blender Plane genannt. Die Materialfarbe der Plane wurde auf gelb und auf eine Transparenz
von 50% (halb durchsichtig) gesetzt [10].
Der Bombenzuender ist eine Zusammenstellungen von Wuerfeln, in Blender Cube genannt, die
entsprechend skaliert, rotiert, verschoben und denen entsprechend die Materialfarben Rot oder
Schwarz zugeordnet wurden.
3.7.2 Programmierung der 3D-Spielelandschaft
Das Spiel sollte nicht einfach aus einem langweiligen Spielbrett mit normalen Spielsteinen
bestehen. Stattdessen wurde die Idee benutzt, eine Wiese mit Bauemen darzustellen, in dessen
Mittelpunkt ein 6x6-Gitter fuer das Spielbrett platziert wird und die Spielsteine Schafe auf dieser
Wiese sind.
3.7.2.1 Erzeugen des Spielfeldes als Gitter
Das Mobile Java 3D API bietet keine Moeglichkeit zur Erzeugung von Primitivobjekten wie
Linien, Kugeln, Dreiecken oder Wuerfeln. Wenn diese Objekte nicht als 3D-Modelle importiert
werden, so koennen sie anhand von Mesh-Objekten programmatisch erzeugt werden. Ein Mesh
ist ein 3D-Objekt, dass aus einer Menge von Vertex-Punkten besteht, aus denen sich das Objekt
zusammen setzt, einer Appearance, die die auessere Erscheinung des Objektes wieder gibt wie
Textur oder Materialeigenschaften und aus einer Menge von Trianglestrips, die angibt in
Seite 21 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
welcher Reihenfolge die Vertices eines Objektes verbunden werden muessen, um das
gewuenschte Objekt zu bilden. Um das 6x6-Spielfeld darzustellen, wurde ein Gitter benutzt, dass
aus 14 Linien besteht (7 horizontal, 7 vertikal).
Abbildung 3-6: Aufbau eines Mesh-Objektes im Koordinatenursprung
Die linke Abbildung 3-6 zeigt, wie ein einzelnes Mesh erzeugt wird, dass die Ausgangsbasis fuer
eine Linie ist. Mit einer TriangleStrip-Laenge von 4 und der Reihenfolge 0,1,3,2, in der die
Indizes verbunden werden, kann dieses Mesh in Form eines Quadrates erzeugt werden. Durch
skalieren des Quadrats in Richtung der x- oder y-Achse kann das Quadrat in die entsprechende
Laenge gezogen werden. Nach Skalierung sieht es aus wie eine Linie. Es werden entsprechend
14 Linien mit dem gleichem Schema im Ursprung erzeugt, entlang der entsprechenden Achse
skaliert
und
an
die
Positionen
so
verschoben,
dass
ein
Gitter
entsteht,
dessen
Linienueberkreuzungen das 6x6-Feld erzeugen.
3.7.2.2 Erstellen der Spielwiese mit einer gekachelten Textur
Als Basis fuer den Untergrund auf dem das Spielfeldgitter sowie die Spielsteine platziert werden,
dient ein einfaches Mesh-Objekt, erzeugt nach dem Schema in Kapitel 3.7.2.1. Jedoch muss
diesem Mesh nun eine Textur zugewiesen werden, damit es optisch als eine Wiese wahr
genommen wird. Weist man dem Mesh einfach eine einzige Textur zu, die sich auf dem
gesamten Untergrund verteilt, so hat das zur Folge, dass aufgrund der geringen Aufloesung die
Textur mit geringem Detail wahr genommen wird. Daher wurde der Untergrund in ein 8x8-Feld
aufgeteilt und jeder Kachel einzeln die Wiesen-Textur zugewiesen, was den Untergrund sehr viel
detallierter aussehen laesst. Die Idee fuer die gekachelte Textur enstand durch das Buch „Killer
Game Programming [5]“. Der Algorithmus dafuer wurde aus dem Buch uebernommen und an
dieses Spiel angepasst.
Alle Vertex-Koordinaten koennen in einer geschachtelten for-Schleife berechnet werden. Die
TriangleStrip-Laenge ist vier, weil ein Feld aus vier Punkten gebildet wird.
Die
Texturkoordinaten werden fuer alle Felder in gleicher Weise fest gelegt. Die Textur wird ueber
Seite 22 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
die Texturkoordinaten in der Reihenfolge (0,1), (1,1), (1,0), (0,0) auf jedem Feld einzeln
zugewiesen.
Abbildung 3-7: Zuweisen einer Textur zu einer Kachel
3.7.2.3 Hinzufuegen von Bauemen zu der Spielelandschaft
Um die Landschaft mit der Wiese etwas lebendiger aussehen zu lassen, wurden Baueme um das
Spielfeld hinzugefuegt. Die Baueme sind keine 3D-Modelle aus Partikelsystemen, wie sie
normalerweise gebildet werden. Die Berechnung auf dem Handy wuerde viel zu lange dauern
und ein fluessiges Spielen unmoeglich machen. Stattdessen wurden Sprite3D-Objekte benutzt.
Ein Sprite3D-Objekt ist einfach ein Mesh, dem sehr einfach ein Bild als Textur zugewiesen
werden kann. Diese kann dann im 3D-Raum bewegt und skaliert werden. Im Gegensatz zu
einem Mesh ist ein Sprite3D-Objekt jedoch immer der Kamera zugewandt, so dass Rotationen
keinen Einfluss auf die Darstellung haben. Bei einer Schraegstellung der Kamera, so wie es in
diesem Spiel der Fall ist, kann damit ein Effekt erzielt werden, so dass das 2D-Bild wie ein 3DBaummodell aussieht. Als Bilder wurden ganz normale Baumbilder benutzt, wobei der
Hintergrund der Bilder transparent ist.
3.7.2.4 Setzen des Lichts und der Kamera
Zum Rendering der Spieleszene wird in der gleichen Weise wie im Spielemenue direktionales
Licht benutzt. Anders als im Spielemenue jedoch muessen die Projektionseigenschaften der
Kamera fuer die Spielelandschaft explizit gesetzt werden, weil die Kamera nicht mehr frontal auf
die x-y-Ebene gerichtet ist und somit 3D-Objekte verzerrt dargestellt wuerden. Mit der Funktion
Seite 23 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Abbildung 3-8: Kamera mit Perspektivprojektion
Quelle: http://www-128.ibm.com/developerworks/wireless/library/wi-mobile1/
SetPerspective (FOVY, aspectRatio, NEAR_CLIPPING_PLANE, FAR_CLIP PING_PLANE)
kann der Modus der Kamera automatisch als Perspektivprojektion gesetzt werden. FOVY
bedeutet Field of View entlang der y-Achse (Viewing Angle auf der Abbildung 3-8), aspectRatio
ist ein Wert fuer das Seitenverhaeltnis der Anzeige des Handys, dass dazu fuehrt, dass Objekte
nicht verzerrt werden. Abbildung 3-8 veranschaulicht die anderen Parameter.
3.7.2.5 Szenegraph der 3D-Spielelandschaft
Abbildung 3-9: Szenegraph der Spielelandschaft
Zusammengefasst ergibt sich der in Abbildung 3-9 gezeigte Szenegraph, der die 3DSpielelandschaft darstellt.
Seite 24 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
3.7.2.6 Visualisierungen zur Anzeige eines Spielerwechsels
Um den Spielern anzuzeigen, wer aktuell an der Reihe ist, wird das Bild eines weissen und
schwarzen Schafskopfes jeweils in der oberen linken und rechten Ecke der Anzeige dargestellt.
Ein gruener Pfeil in Richtung eines der beiden Koepfe zeigt an, welcher Spieler an der Reihe ist.
3.8 Verschiedene Renderstrategien fuer Spielzuege im Paket renderer
Das Paket graphics3d ist nur zur Darstellung der Spielelandschaft zustaendig und bietet
Funktionen fuer das Rendering der Szene in ein Hintergrundbild oder auf der Anzeige und fuer
das Rendering von einzelnen oder mehreren Spielsteinen in einer Gruppe. Die grafischen
Animationen zum Setzen und Drehen von Spielsteinen werden vom Board3D an das Paket
renderer uebergeben, dass aus der abstrakten Klasse Renderer und deren abgeleitete Klassen
RendererNormalSet, RendererFlySet und RendererBombSet besteht.
3.8.1.1 Einfaches Ersetzen der Spielsteine
Ueber den RendererNormalSet wird die einfachste Animation zum Setzen und Umdrehen der
Steine durch gefuehrt. Dieser fuegt einfach den Stein, der gesetzt werden soll dem Szenegraphen
hinzu, entfernt die Steine, die gedreht werden sollen und ersetzt diese durch Hinzufuegen von
Spielsteinen der zu setzenden Farbe in den Szenegraphen. Danach kann der gesamte Szenegraph
im Retained Mode gerendert werden, da beim RendererNormalSet nur ein einziges
Mal
gerendert wird.
3.8.1.2 Fliegende Schafe zur Animation eines Zuges
Anders ist es beim RendererFlySet. Der RendererFlySet bildet ein Group-Objekt, dass aus 3DSpielsteinen besteht. Diese Steine beinhalten das Schaf, das an die gewaehlte Position gesetzt
werden soll und je ein Schaf als Ersatz fuer das Schaf, die in die jeweilige Farbe gedreht werden
muss, weil es geschlagen wurde. Diese Gruppe von Schafen fliegt vom oberen Teil der Anzeige
nach unten auf die Schafe, die in die gegnerische Farbe gedreht werden muessen. Waehrend die
Gruppe von Schafen nach unten fliegt, wird Immediate Mode Rendering benutzt, da mehrmals
gerendert werden muss bis die Schafe auf der Wiese landen. Retained Mode waere hier
ineffizient. Nachdem die Schafe auf der Wiese angekommen sind, scheint es visuell, dass die
entsprechenden Steine nun in die gegnerische Farbe umgewandelt sind. Jedoch sind sie intern
noch im Szenegraphen. Daher werden diese nachdem die Gruppe auf der Wiese gelandet ist vom
Szenegraphen entfernt und mit der Gruppe ersetzt. Danach wird der gesamte Graph im Retained
Seite 25 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Mode in ein Bild gerendert, dass bei der naechsten Zuganimation im Immediate Mode zum
Loeschen des Hintergrundes benoetigt wird.
Abbildung 3-10: Zuganimation mit fliegenden Schafen
3.8.1.3 Explosion der Schafe beim Durchfuehren eines Zuges
Abbildung 3-11: Zuganimation mit explodierenden Schafen
Der RendererBombSet fuehrt dazu, dass beim Setzen eines Steines zuerst der Bombenzuender
durch Translationen an die Stelle verschoben wird, an die gesetzt werden soll. Danach fliegt ein
Schaf der Farbe, die gesetzt werden soll auf den Bombenzuender, Kommt das Schaf auf dem
Bombenzuender an, so explodieren die gegnerischen Schafe, die gedreht werden muessen. Das
Explodieren der Schafe wird dadurch simuliert, dass an die Stelle der Schafe, die gedreht werden
sollen, ein Sprite3D-Objekt mit einem Bild einer halbstransparenten Rauchwolke gerendert wird
und nach dem Verschwinden der Wolke das Schaf in der zu setzenden Farbe vorliegt. Auch hier
wird wieder Immediate Mode Rendering benutzt um den Bombenzuender an die entsprechende
Stelle zu verschieben und die Rauchwolken zu zeichnen.
3.9 Netzwerkimplementierung des Spiels
Mit dem Spiel ist es moeglich, sich auf einem zentralen Spieleserver, der ueber das Internet zu
erreichen ist, einzuwaehlen und ein Spiel gegen andere eingewaehlte Spieler entfernt zu starten.
Dazu gibt es das Paket server, dass die Implementierung des Spieleservers beinhaltet und das
Paket network, das die Schnittstelle zum Server bildet.
Seite 26 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
3.9.1 Game Server im Paket server
Das Paket server ist nicht Teil des Spiels, was auf dem Handy installiert wird. Es wird separat
auf einem Rechner ausgefuehrt. Es benutzt die normale J2SE-Bibliothek in der Version 1.4.2.
Das Paket besteht aus den drei Klassen ConnectionsListener, GameServer und PlayerLinker. Der
GameServer
ist
fuer
die
Verwaltung
der
angemeldeten
Spieler
zustaendig.
Der
ConnectionListener ist fuer die Annahme von Einwahlanfragen von Seiten des Clients
zustaendig. Ein PlayerLinker uebernimmt die Kommunikation zwischen zwei Spielern, wenn
diese ein Spiel gegeneinander gestartet haben und bildet die Netzwerkschnittstelle zum Server.
Der GameServer enthaelt die Main-Methode und wird bei Programmstart aufgerufen. Eine
Portnummer , auf dem der Server laufen soll, kann ueber geben werden. Ansonsten wird der
DEFAULT_PORT 5647 benutzt (siehe Anhang A.4 Bedienung des Servers). Anzumerken ist,
dass der Port auf dem Client auch geaendert werden muss, wenn ein anderer Port als der
Default-Port benutzt werden soll. Der Server sollte aber im realen Einsatz immer auf dem
gleichen Port laufen. Der GameServer besitzt die innere Klasse CommandInput ueber den der
Administrator Befehle auf der Kommandozeile eingeben kann, die den Server beenden oder alle
verfuegbaren Spieler und Befehle ausgeben. Der ConnectionListener erzeugt einen ServerSocket
mit new ServerSocket (portNumber), auf dem er in einem eigenen Thread auf
Verbindungsanfragen
horcht.
Trifft
eine
Verbindungsanfrage
ein,
so
leitet
der
ConnectionListener den durch serverSocket.accept () geoeffneten Socket an den GameServer
weiter, der die Verbindungen kontrolliert. Der GameServer erzeugt fuer jeden Socket ein
PlayerLinker-Objekt. Ueber einen PlayerLinker wird die Verbindung zwischen zwei Spielern
aufgebaut. Es ist der Kommunikationskanal eines Spiels. Der Server beschraenkt die maximale
Anzahl von Spielern, die sich einwaehlen koennen auf die Konstante MAX_NUMBER_PLAYERS
mit dem Wert 30.
3.9.2 Netwerkclient im Paket network
Das Paket network besteht aus den beiden Klassen OpponentChooser und PlayerConnector. Der
OpponentChooser stellt eine GUI auf dem Handy zur Verfuegung, ueber den sich ein Spieler auf
dem Server anmelden, die verfuegbaren Spieler sehen und einen Spieler zu einem Spiel einladen
kann. Dazu muessen Nachrichten zum Server nach einem bestimmten Protokoll zwischen Client
und Server verschickt werden. Die Nachrichten fuer das Protokoll werden ueber den
PlayerConnector versendet und empfangen, der die Protokollimplementierung auf der ClientSeite darstellt. Auf der Serverseite uebernimmt die Implementierung der PlayerLinker. Der
Seite 27 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
PlayerConnector laeuft in einem eigenen Thread, damit ein Blockieren des Clients verhindert
wird.
3.9.3 Senden und Empfangen von Daten
Das Senden und Empfangen von Daten auf dem Handy-Client und dem Server erfolgt ueber
Sockets. Jedoch wird in J2ME ein Socket anders erzeugt als bei J2SE. J2ME stellt die
Schnittstelle javax.microedition.io.SocketConnection zur Vefuegung. Ein Socket kann mit
SocketConnection playerSocket = (SocketConnection) javax.microedition.io.Connector.open
(socket://server:port)
geoeffnet
werden.
Mit
java.io.DataInputStream
receiver
=
playerSocket.openDataInputStream () und java.io.DataOutputStream sender = playerSocket
.openDataOutputStream () koennen die Streams zum Lesen und Schreiben auf den Socket
geoeffnet werden. Auf der Server-Seite wird der Socket mit Socket playerSocket =
serverSocket.accept () geoeffnet. Die Streams zum Lesen und Schreiben koennen mit
BufferedReader receiver =
new BufferedReader (new InputStreamReader (playerSocket.
getInputStream () und PrintStream sender = new PrintStream (playerSocket.getOutputStream
()). Das Lesen und Schreiben von Daten ueber die erzeugten Streams kann bei J2ME und J2SE
in gleicher Weise erfolgen. Ein String wird Zeichen fuer Zeichen gesendet und empfangen [12].
3.9.4 Kommunikationsprotokoll zwischen Client und GameServer
Das Kommunikationsprotokoll zwischen dem Client, der in Form des PlayerConnector vor liegt
und zwischen dem Server in Form des PlayerLinker besteht aus den 11 Zeichenkettenkonstanten
PLAYER_QUIT, LOGOUT_REQ, LOGOUT_ACK, LOGIN_REQ, LOGIN_REJ, LOGIN_ACK,
GAME_REQ, GAME_REJ, GAME_ACK, TIMEOUT und SERVER_QUIT. (REQ = Request,
ACK = Acknowledgement, REJ = Rejection)
3.9.4.1 Login und Logout eines Spielers
Abbildung 3-12: Erfolgreiches Einwaehlen auf dem Server
Seite 28 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Der Client schickt zunaechst ein LOGIN_REQ zusammen mit dem Namen, mit dem er sich auf
dem Server einwaehlen moechte. Ist der Name nicht vergeben, sendet der Server ein
LOGIN_ACK zusammen mit den Spielern als Zeichenkette, die gerade eingewaehlt sind und
nicht selber spielen (Abbildung 3-12).
Abbildung 3-13: Nicht erfolgreiches Einwaehlen auf dem Server
Existiert der gewuenschte Benutzername schon, so erhaelt der Client ein LOGIN_REJ
(Abbildung 3-13). Existieren keine verfuegbaren Spieler, wird nur die Nachricht LOGIN_ACK
ohne zusaetzlicheInformationen vom Server gesendet.
Beim Abmelden vom Server, wird die Nachricht LOGOUT_REQ gesendet, die der Server mit
LOGOUT_ACK bestaetigt und danach den Spieler von der Liste der verfuegbaren Spieler
entfernt.
3.9.4.2 Einladen eines Spielers zu einem Spiel
Abbildung 3-14: Einladung zu einem Spiel
Um einen Spieler zu einem Spiel einzuladen sendet der Client ein GAME_REQ zusammen mit
dem Namen des Spielers, gegen den er spielen moechte. Der Server wertet die Anfrage aus und
schickt dem anderen Client nun die Nachricht GAME_REQ zusammen mit dem Namen des
Seite 29 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Spielers von dem die Anfrage kommt. Stimmt der eingeladene Spieler zu, dann sendet er ein
GAME_ACK ueber seinen PlayerLinker an den Client des einladenden Spielers (Abbildung 314). Lehnt der eingeladene Spieler ab, wird anstatt des GAME_ACK ein GAME_REJ gesendet.
Ist der eingeladene Spieler in dem Augenblick, in dem er eingeladen wird fuer die Dauer von 20
Sekunden nicht anwesend und antwortet nicht auf die Einladung, so schickt der PlayerLinker des
einladenden Spielers ein TIMEOUT sowohl an den einladenden als auch an den eingeladenen
Spieler, damit das Einladungsfenster bei diesem automatisch geschlossen werden kann
(Abbildung 3-15).
Abbildung 3-15: Timeout bei der Spieleinladung
3.9.4.3 Versenden von ausgefuehrten Zuegen
Nachdem ein Spiel erfolgreich gestartet wurde, werden nur noch Zuege zwischen den Spielern
versendet. Es wird jeweils bei jedem Spielzugwechsel eine Zeichenkette versendet, der nur aus
zwei Zahlen besteht, z.B. „32“, die erste Zahl gibt die x-Koordinate des Zugs an, die zweite die
y-Koordinate (Abbildung 3-14). Da die meisten Handys ueber GPRS (General Packet Radio
Service) Daten im Internet versenden, koennen auch Leute, die keine Internet-Flatrate fuer ihr
Handy haben, trotzdem sehr guenstig ein Spiel spielen, weil bei GPRS nach Datenmengen, die
gesendet werden, abgerechnet wird und nicht nach der Zeit, die man online ist.
3.9.4.4 Beenden eines Netzwerkspiels
Abbildung 3-16: Spielende bei Serverstop
Seite 30 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Ein Netzwerkspiel wird beendet, wenn entweder der Server beendet wird oder ein teilnehmender
Spieler ueber die Return-Taste das Spiel vorzeitig verlaesst. Beendet der Administrator den
Server, so wird automatisch an alle eingewaehlten Spieler ein SERVER_QUIT gesendet.
Beendet ein Spieler vorzeitig ein Spiel, so sendet sein Client in Form des PlayerConnector ein
LOGOUT_REQ an den Server, der diesen von der Liste der verfuegbaren Spieler loescht und ein
PLAYER_QUIT an den gegnerischen Spieler sendet, der darauf auch mit einem LOGOUT_REQ
reagiert. Auf ein LOGOUT_REQ gibt es immer ein LOGOUT_ACK vom Server zurueck.
Abbildung 3-17: Spielende beim Auswaehlen eines Spielers
Es werden keine Bestaetigungen fuer das SERVER_QUIT oder PLAYER_QUIT versendet. Sollte
der Spieler das Spiel nicht auf regulaerem Weg beenden (z.B. schaltet er das Handy komplett
aus, ohne davor auf die Return-Taste zu druecken), so erkennt das der Server und sendet
automatisch ein PLAYER_QUIT an den gegnerischen Spieler, der sich danach automatisch vom
Server auf dem regulaeren Weg mit LOGOUT_REQ abmeldet.
3.10 Spielsound durch das Paket sound
Das Paket sound besteht aus der Klasse SoundPlayer. Dieser bietet die Moeglichkeit AudioDateien abzuspielen. Alle Audiodateien, die benutzt werden, sind im MP3-Format. Es gibt
Audiodateien fuer jedes Spielereignis. Bei Start des Spiels wird ein Stueck von dem Lied „Let’s
get it started“ von der Gruppe Black Eyed Peas gespielt. Wenn ein Spieler ein Netwerkspiel
spielt oder ein Spiel gegen das Handy, so wird je nachdem, ob er gewonnen oder verloren hat ein
Stueck von „We are the champions“ von der Gruppe Queen und bei Niederlage „I’m a loser
baby“ der Gruppe Beck abgespielt. Weiterhin erscheinen Toene, wenn ein Spieler das
Seite 31 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
ausgewaehlte Spielfeld aendert, auf einem nicht gueltigen Spielfeld fuer ihn setzen will oder
einen gueltigen Zug durchfuehrt.
4 Aufgetauchte Probleme
Das Projekt wurde kontinuierlich von Problemen begleitet, die aber (zum Glueck) alle geloest
werden konnten. Das groesste Problem bei diesem Projekt war die mangelnde Dokumentation
fuer die mobile 3D-Programmierung nach JSR-184-Spezifikation im Internet. Es gibt zur Zeit
(immer noch) kein einziges Buch ueber dieses Thema. Daher musste sehr vieles alleine aus der
Spezifikation abgeleitet werden. Jedoch wurde auch viel Wissen ueber die Sony Ericsson
Developer-Homepage angeeignet, die viele Code-Beispiele zum besseren Verstaendnis liefert.
Weiterhin ist die implementierte Version der Spezifikation auf dem Simulator und Handy nicht
fehlerfrei. Jedoch konnten die Probleme ueber Informationen im Mobile Java 3D-Forum auf der
Sony Ericsson Developer-Homepage umgangen werden. Zum Beispiel muessen redundante
Aufrufe bei Initialisierung eines Szenegraphen ausgefuehrt werden bevor ein Kind von diesem
zum ersten Mal entfernt werden kann (siehe dazu graphics3d.Object3DLoader.java im Anhang
A.6). Ein weiteres Problem war, dass das Spiel sehr oft auf das reale Handy kopiert werden
musste, was insgesamt sehr zeitintensiv war (jar-Datei erzeugenÆauf das Handy kopierenÆauf
dem Handy installierenÆSpiel starten), weil es sehr oft gemacht werden musste. Der Grund
dafuer ist, dass das Spiel im Simulator auf dem Rechner schneller laueft sowie manche
grafischen Elemente wie Formulare im Simulator anders dargestellt werden wie auf dem Handy.
So konnten waehrend der Entwicklung schon Engpaesse auf dem realen Handy erkannt werden
und optimiert werden. Das Risiko, dass am Ende der Entwicklung das Spiel auf dem realen
Handy oder nicht fluessig oder mit einer anderen Optik ablaueft, wurde dadurch vermeidet. Da
man nach dem Einlesen von 3D-Objekten im Programm immer nur ueber die jeweilige ID auf
das Objekt zugreifen kann, musste man bei jedem Exportieren der Modelle aus Blender3D und
das anschliessende Einlesen jedes mal neu ausprobieren, unter welchen ID’ s sich die 3DModelle befinden. Dieses Problem wurde dadurch versucht zu loesen, in dem nicht alle 3DModelle in eine einzige M3G-Datei exportiert wurden, sondern mehrere Dateien mit weniger
Objekten angelegt wurden. Die modellierten 3D-Objekte mit Blender fuer dieses Spiel sind ganz
bestimmt keine komplexen 3D-Objekte. Jedoch hat es sehr lange gedauert, bis die Objekte so
modelliert werden konnten, dass ihre Polygonanzahl so angepasst wurde, dass das Rendering
dieser Objekte schnell durchgefuehrt werden konnte und somit ein fluessiges Spielen erlaubte.
Seite 32 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
5 Abschliessende Bemerkung
Das Projekt wurde erfolgreich in dem gegebenen Zeitrahmen bearbeitet. Es ist ein 3D-ReversiHandyspiel entstanden, dass einem menschlichen Spieler erlaubt, gegen einen anderen
menschlichen Spieler auf dem gleichen Handy, gegen das Handy (kuenstliche Intelligenz) oder
gegen einen anderen entfernten menschlichen Spieler ueber einen zentralen Spieleserver im
Internet zu spielen. Damit wurden die anfangs fest gelegten Ziele vollkommen erreicht und
durch Implementierung einer kuenstlichen Intelligenz sowie Netzwerkfaehigkeit des Spiels sogar
uebertroffen. Der Quellcode des Spiels inklusive Spieleserver besitzt ungefaehr 8000 Zeilen
(5000 Zeilen Programmcode, 3000 Zeilen Kommentare). Die jar-Datei zum Installieren des
Spiels auf dem realen Handy, hat inklusive der 3D-Modelle fuer die Menues und die
Spielelandschaft, allen Bildern, die benoetigt werden sowie den Audiodateien
eine
Gesamtgroesse von nur 444 KB.
Mobile 3D-Programmierung ist noch ein sehr junges Thema, was den Mangel an Literatur
begruendet. Fuer alle, deren Interesse an diesem Thema nach Lesen dieser Arbeit geweckt oder
verstaerkt wurde, sei hier auf das Buch „Mobile 3D Graphics, Learning 3D Graphics with the
Java Micro Edition“ von Claus Hoefele verwiesen, das demnaechst veroeffentlicht wird.
Seite 33 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Anhang
Seite 34 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Anhang A.1 Einfuehrung in die mobile 3D – Programmierung
Kurze Einfuehrung in J2ME
Da eine detaillierte Beschreibung der Java Platform, Micro Edition den Rahmen dieser Arbeit
sprengen wuerde, soll im Folgenden nur beschrieben werden, wie J2ME aufgebaut ist und wie
man ein J2ME-Programm schreibt.
Es gibt drei Grundelemente , aus denen die J2ME-Technologie besteht: Eine Konfiguration
bietet einen Basissatz an Bibliotheken und Faehigkeiten einer virtuellen Maschine fuer viele
Arten von mobilen Endgeraeten. Ein Profil baut auf einer Konfiguration auf und unterstuetzt nur
einen eingeschraenkten Kreis von Geraeten (z.B. nur Handys oder nur PDA´s). Auf den voran
gegangen beiden Bibliotheken baut dann ein optionales Paket auf, dass technologie-spezifische
Applikationsschnittstellen beinhaltet. Ein solches Paket kann z.B. das Mobile Java 3D-API
beinhalten, was im naechsten Abschnitt beschrieben wird.
Es gibt im Moment zwei Arten von Konfigurationen. Die Connected Limited Device
Configuration (CLDC) ist fuer ressourcenaermere mobile Endgeraete gedacht (z.B. Handys)
wobei die Connected Device Configuration (CDC) auf leistungsfaehigere Endgeraete zielt (z.B.
Smartphones). Da CLDC in Handys Einsatz findet, werden entsprechende Klassen in der
Entwicklung benutzt, auf die in der Detaildokumentation einge-gangen wird. Das bekannteste
Profil, das auf CLDC aufbaut, ist das Mobile Information Device Profile (MIDP), das als Version
2.0 zur Verfuegung steht. Es gibt entsprechend viele verschiedene Toolkits, die dann die
zusaetzliche Funktionalitaet fuer die
einzelnen Geraete von verschiedenen Herstellern zur
Verfuegung stellen. (siehe Anhang A.2). CLDC zusammen mit MIDP ist heutzutage die am
meisten verwendete Kombination, mit der Handy-Software implementiert wird [2].
Abbildung A-1: Java Platform
Quelle: http://java.sun.com/javame/technology/
Seite 35 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Wie die oeffentliche Klasse mit der public static void main (String args [])-Methode der
Einstiegspunkt fuer ein Programm bei J2SE ist, so ist der Einstiegspunkt bei J2ME eine Klasse,
die die Klasse javax.microedition.midlet.MIDlet erweitert. Dabei muss man die geerbten
abstrakten Methoden startApp (), pauseApp () und destroyApp () ueberschreiben. Nach
Ausfuehren des Programms wird dabei automatisch der Konstruktor der Klasse und danach die
Methode startApp () aufgerufen. Die Methode pauseApp () wird z.B. aufgerufen, wenn ein
Telefonanruf eingeht waehrend ein Midlet laeuft, und destroyApp () wird aufgerufen sobald das
Midlet beendet wird.
Einfuehrung in das „Mobile 3D Graphics API“
Das Mobile 3D Graphics API, kurz M3G, nach JSR-184-Spezifikation ist ein neuer JavaStandard, der es ermoeglicht, interaktive 3D-Grafiken zur Nutzung auf mobilen Endgeraeten
darzustellen. Dabei umfassen die Applikation, die entwickelt werden koennen Spiele, animierte
Nachrichten, Bildschirmschoner, Benutzeroberflaechen, usw. Die Schnittstelle wird in
Kombination mit J2ME benutzt und setzt wie oben schon erwaehnt auf CLDC und MIDP auf.
Der Standard wurde Ende Oktober 2003 verabschiedet und ist somit erst seit 3 Jahren
verfuegbar. Die Schnittstelle ist nur 150 KB gross und nur eine Teilmenge von OpenGL ohne
zusaetzliche Funktionalitaet. Sie befindet sich in dem Paket javax.microedition.m3g, falls das
entsprechende Toolkit die Schnittstelle unterstuetzt. Das Paket enthaelt insgesamt 30 Klassen.
Abbildung A-2: Szenegraph nach JSR-184-Spezifikation
Quelle: JSR-184-Spezifikation
Seite 36 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Abbildung A-2 zeigt ein Beispiel, aus welchen Elementen ein Szenegraph nach M3G aufgebaut
werden kann. Eine Hierarchie wird von Objekten der abstrakten Klasse Node gebildet mit einem
Objekt der Klasse World als Wurzelelement. Die Objekte duerfen dabei keine Zyklen bilden.
Alle in der Abbildung gezeigten Klassen ausser World erweitern die Klasse Node. Fuer eine
komplette Beschreibung aller Klassen sei auf [1] verwiesen.
Es besteht die Moeglichkeit neben der programmatischen Erzeugung von 3D-Objekten, diese
auch mit einem Modellierungswerkzeug zu entwerfen und im Programm einzulesen. Fuer M3G
wurde daher ein eigenes Format entwickelt, das 3D-Objekte sehr kompakt fuer die Darstellung
auf mobilen Endgeraeten als Szenegraph in einer Datei mit der Endung .m3g speichert. Fuer
einige Modellierungswerkzeuge gibt es Exportierer, mit denen sich 3D-Modelle im M3G-Format
speichern lassen.
Laden von 3D - Modellen
Das Laden von 3D-Modellen geschieht ueber die Klasse Loader. Diese besitzt die zwei
statischen Methoden static Object3D [] load (String name) und static Object3D [] load (byte [],
int offset). Mit der zweiten Methode kann programmatisch ein 3D-Objekt aus byte-Werten
deserialisiert werden. Fuer das Projekt spielt jedoch nur die erste Methode eine Rolle und wird
daher naeher beschrieben.
Als Argument bekommt die Methode einfach den Pfad zur M3G-Datei, die den Szenegraphen
inklusive 3D-Objekte, Lichter, Kameras, Texturen, Materialien, usw. und sogar Animationen
enthalten kann, uebergeben. Die Methode gibt dann ein Array aus Object3D-Objekten zurueck,
das die einzelnen 3D-Modelle enthaelt, falls diese gesetzt sind. Object3D-Objekte sind alle
Objekte, die Teil einer 3D-Welt sein koennen. Jedoch ist die Reihenfolge, unter welchem Index
sich die Objekte im Array befinden nicht definiert. Das Problem wird dadurch geloest, dass den
einzelnen 3D-Objekten eindeutige Identifikationsnummern beim Exportieren ins M3G-Format
durch den entsprechenden Exportierer vergeben werden (siehe Anhang A.2). Ueber ein
entsprechenden M3G-Betrachtungsprogramm kann man sich die Struktur einer M3G-Datei
anschauen und so die einzelnen Identifikationsnummern der Objekte heraus finden. Hat man die
ID des Objektes, kann man ueber die Methode Object3D find (int userId), die World von
Object3D erbt, auf das 3D-Objekt durch entsprechende Typ-Konvertierung zugreifen. Dazu
muss man jedoch mindestens Zugriff auf das World-Objekt des Szenegraphen haben. Diese kann
jedoch dadurch erhalten werden, wenn man das gesamte Object3D[]-Array nach einem Objekt
des Typs World durchsucht. Danach kann z.B. mit (Camera) world.find (14) auf die Kamera mit
der ID 14 des Szenegraphen zugegriffen werden. Die so gefundenen 3D-Objekte stehen danach
Seite 37 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
zur Verfuegung und koennen z.B. verschiedenen Transformationen unterzogen und danach auf
der Anzeige gerendert werden, was im naechsten Abschnitt beschrieben ist.
Rendering von 3D-Objekten
Bei M3G wird zwischen zwei Rendering-Verfahren unterschieden. Zum einen ist es moeglich,
den kompletten Szenegraphen mit all seinen Objekten auf einmal zu rendern, was als Retained
Mode Rendering bezeichnet wird. Zum anderen ist es moeglich, nur Teile eines Graphen zu
rendern, was als Immediate Mode Rendering bezeichnet wird. Das letztere ist im Hinblick auf
Performanz sehr viel leistungsstaerker, da man die Kontrolle darueber hat, welche Objekte neu
gerendert werden muessen und somit nicht der komplette Graph jedes Mal neu gerendert werden
muss. Jedoch ist die Benutzung schwieriger, da der Hintergrund der Anzeige im Retained Mode
automatisch gesetzt wird und man im Immediate Mode selber eine Strategie zum Loeschen des
Hintergrundes ueberlegen muss, um eine gute Performanz zu erreichen.
Der Szenegraph oder einzelne Objekte werden mittels der Klasse Graphics3D auf der Anzeige
gerendert. Diese nutzt das eigentliche javax.microedition.lcdui.Graphics-Objekt, dass den
Zugriff auf die Anzeige ermoeglicht.
Um ein 3D-Objekt im Retained Mode auf der Anzeige sichtbar zu machen, muss zunaechst ein
Graphics-Objekt an das Graphics3D-Objekt mit der Methode void bindTarget (Graphics g) des
Graphics3D-Objektes gebunden werden.
Danach kann mit der Graphics3D-Methode void
render (World scene) der komplette Graph gerendert werden. Zum Rendern wird dabei die
Kamera benutzt, die im Szenegraphen gesetzt ist. Im Anschluss daran muss das Graphics-Objekt
mit der Graphics3D-Methode void releaseTarget () wieder frei gegeben werden.
Im Unterschied dazu muss im Immediate Mode die Kamera explizit fuer das Graphics3D-Objekt
neu gesetzt werden, und kann z.B. einmal zu Programmbeginn gemacht werden. Bevor das
Rendering statt findet, muss man explizit den Hintergrund mit der Graphics3D-Methode void
clear (Background back) loeschen. Danach erfolgt das Rendering des Objektes mit void render
(Node node, Transform trans), wobei man mit dem Paramater trans das Objekt vor dem
Rendering noch einmal einer Transformation unterziehen kann. Nach dem Rendern muss auch
hier wieder das Graphics-Objekt frei gegeben werden (siehe Anhang A.6 Klasse
graphics3d.Board3D.java) Es gibt noch weitere Rendering-Funktionen fuer den Immediate
Mode. Diese werden jedoch nicht naeher erlaeutert, weil Sie fuer dieses Projekt irrelevant sind
und koennen in der Spezifikation [1] nachgelesen werden.
Seite 38 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Anhang A.2 Auswahl und Einsatz von Werkzeugen
Zur Realisierung des Projektes wurden ausschliesslich frei verfuegbare Werkzeuge benutzt. Um
J2ME-Programme fuer ein mobiles Endgeraet zu schreiben, muss, wie schon erwaehnt wurde,
das entsprechende Toolkit des Herstellers benutzt werden. Da waehrend dem Projekt ein Handy
des Modells SonyEricsson W810 zur Verfuegung steht, auf das das Spiel portiert werden sollte,
wurde das Sony Ericsson SDK benutzt. Dieses Toolkit kann man in der EclipseEntwicklungsumgebung ueber das Plugin eclipseME einbinden und somit J2ME-Programme in
der Umgebung von Eclipse schreiben. Zum Modellieren von entsprechenden 3D-Modellen
wurde das Modellierungstool Blender benutzt. Um die Modelle ins M3G-Format zu exportieren
wurde ein entsprechendes M3G-Exportierer-Plugin fuer Blender benuzt, das automatisch die
ID´s
fuer die einzelnen Objekte vergibt.
Abbildung A-3: Objekt-ID-Baum des Szenegraphen
Um die im Szenegraphen enthaltenen Objekte und ihre ID´s zu betrachten wurde der M3GBetrachter der Firma HI Corporation benutzt, was auch frei erhaeltlich ist. Abbildung A.2-1 zeigt
den Szenegraphen der in diesem Projekt modellierten und benutzten 3D-Objekte. Zum
Bearbeiten entsprechender Bilder wurde GIMP benutzt. Da neben der 3D-Grafik das
Spielerlebnis auch noch durch akustische Effekte gesteigert
werden sollte, wurden entspre-
chende Audio-Daten mit dem Audiowerkzeug Audacity bearbeitet. Zum Erstellen von
Klassendiagrammen wurde das UML-Modellierungsprogramm Visual Paradigm benutzt.
Es folgt eine Liste der Versionen der freien Werkzeuge, die benutzt wurden:
-
Eclipse:
Version 3.2.1
-
Sony Ericsson SDK:
Version 2.2.4
Seite 39 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
-
EclipseME-Plugin:
Version 1.5.5
-
M3G-Betrachter:
Version 3.5
-
GIMP:
Version 2.2.3
-
Audacity:
Version 1.2.6
-
Visual Paradigm:
Version 2.3 Community Edition
-
Blender3D:
Version 2.42a
-
Blender-M3G-Plugin:
Version 0.6
Diese Werkzeuge befinden sich alle auf der mit gelieferten CD.
Anhang A.3 Portierung des Spiels auf ein reales Handy
Das Spiel kann auf alle SonyEricsson Handy-Modelle portiert werden, die eine 3D-Engine
besitzen. Da zur Entwicklungszeit keine 3D-Handys von anderen Herstellern wie Nokia oder
Samsung zur Verfuegung standen, konnte eine Portierung auf diesen Geraeten nicht
durchgefuehrt werden. Daher kann nicht die Garantie gegeben werden, dass das Spiel auf nicht
SonyEricsson-Handys gespielt werden kann.
Fuer alle, die ein SonyEricsson-Handy mit 3D-Engine besitzen, sind folgende Portierungsschritte
notwendig. Dies ist die Anleitung fuer das Modell W810, sollte aber auch bei allen aktuellen
SonyEricsson in gleicher oder aehnlicher Weise funktionieren:
1. Verbinde das Handy per USB-Kabel mit dem Rechner.
2. Kopiere die Datei MobileReversi3D.jar in den Ordner „other“ des Handys.
3. Installiere das Spiel auf dem Handy ueber Menu Æ Datei-Manager Æ Andere Æ Datei
MobileReversi3D auswaehlen Æ Installieren.
4. Starte das Spiel ueber Menu Æ Unterhaltung Æ Spiele Æ MobileReversi3D.
Wer die jar-Datei selber erzeugen moechte, muss zuerst die Entwicklungsumgebung in Eclipse
einrichten. Dazu muss zuerst das EclipseMe-Plugin und danach das SonyEricsson-SDK (Æ gibt
es nur fuer Windows) installiert werden. Danach kann das Projekt in Eclipse importiert werden
und in der Package Explorer-Ansicht ueber Rechtsklick auf das Projekt MobileReversi3D Æ
J2ME Æ Create Pakage die jar-Datei erzeugt werden.
Installation von EclipseMe und das Einbinden des Toolkits SonyEricsson SDK :
http://eclipseme.org/docs/installation.html,
Die Seiten stehen auch als Offline-Version auf der mit gelieferten CD zur Verfuegung.
Seite 40 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Anhang A.4 Starten und Bedienen des Gameservers
Der Spieleserver wurde auf den beiden Betriebssystemen Windows XP und Solaris getestet, die
auf den Rechnern der Hochschule Fulda im CAE-Labor installiert sind. Weil keine zusaetzlichen
Bibliotheken fuer das Server-Programm und nur das Java SDK 1.4.2 benutzt wurden, sollte es
aber auch auf anderen Windows-Versionen und Unix-Derivaten mit den folgenden Befehlen
ohne Probleme zum Laufen gebracht werden. Gilt sowohl fuer Windows als auch fuer Solaris:
1. Auf der Kommandozeile in den Ordner wechseln, in dem sich der Ordner (server) mit
den java-Dateien fuer den Server befindet.
2. Um die Klassen-Dateien zu erzeugen javac server\GameServer.java eingeben.
3. Danach mit java server.GameServer [portNummer] den Server starten. Die Portnummer
ist optional. Wird keine angegeben, ist der Port 5647 vorgegeben.
Der Server kann natuerlich auch direkt unter Eclipse gestartet werden, wenn das Projekt
importiert wurde.
Nachdem der Server gestartet wurde, kann er mit dem Kommandozeilenbfehl quit regulaer
beendet werden. Mit show players kann eine Liste aller eingewaehlten Spieler angezeigt werden.
Mit ? kann eine Liste der moeglichen Server-Befehle ausgegeben werden, in diesem Fall nur die
zwei erwaehnten quit und show players.
Anhang A.5 Zeitaufwand fuer den Entwicklungsprozess
Bereich
Stunden
Einarbeitung in J2ME, Mobile 3D Graphics
20
API, Blender3D, Alpha-Beta-Algorithmus
Modellierung aller benoetigten 3D-Objekte mit
20
Blender, so dass sie auf dem Handy fluessig
gerendert werden koennen
Bearbeitung aller benoetigten Bilder mit Gimp
15
Bearbeitung der Audiodateien mit Audacity
5
Programmierung des Spiels
360
Dokumentation + Praesentation
80
Total
~ 500 Stunden
Tabelle A-1: Zeitaufwand fuer den Entwicklungsprozess
Seite 41 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Anhang A.6 Quellcode des Spiels
Paket AI
BoardSimulator.java
package ai;
import reversi.Board;
import java.util.Stack;
/**
* Simuliert das Setzen eines Steins auf dem Brett. Der Stein kann
* gesetzt werden. Anschliessend kann der Zug wieder rueckgaengig
* gemacht werden. Zum Speichern der Zuege wird ein Stack benutzt.
* Diese Klasse wird von der Klasse IntelligenceHard benutzt, um eine
* bestimmte Anzahl von Zuegen zu simulieren sowie rueckgaengig zu
* machen, um wieder zur Ausgangsstellung zurueck zu kehren.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;BoardSimulator.java<br>
* <b>Date:</b>08.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
class BoardSimulator extends Board
{
/**
* Stack zum Speichern von Zuegen.
*/
private Stack setStack;
/**
* Initialisiert den Stack zum Speichern von Zuegen.
*/
public BoardSimulator ()
{
setStack = new Stack ();
}
/**
* Macht den Zug, der als letztes gemacht wurde rueckgaengig. Dafuer
* entfernt es den letzten Zug der im Stack setStack, in dem alle
* zuvor gemachten Zuege gespeichert sind, als letztes hinzugefuegt
* wurde.
*/
public void undoLastSet ()
{
byte [][] lastDeletedPositions
= (byte [][]) setStack.pop ();
int
numberDeletedPositions = lastDeletedPositions.length;
byte []
lastSet
= (byte []) setStack.pop ();
board [lastSet [0]][lastSet [1]] = Board.EMPTY;
for (byte i = 0; i < numberDeletedPositions; i++)
{
board [lastDeletedPositions [i][0]]
[lastDeletedPositions [i][1]] = lastDeletedPositions [i][2];
};
}
Seite 42 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
/**
* Setzt die Variable board, die das Spielbrett als 2D-byte-Array
* dar stellt auf die uebergebene Variable board, auf dem die
* Simulation statt findet.
*
* @param board Stellt das Brett dar, auf dem Zuege simuliert werden.
*/
public void setBoard (byte [][] board)
{
this.board = board;
}
/**
* Simuliert einen Zug, in dem es den Zug macht, aber speichert
* zusaetzlich die Koordinaten fuer den Zug und alle Steine, dessen
* Farben geaendert wurde, damit der Zug auch wieder rueckgaengig
* gemacht werden kann.
*
* @param x X-Koordinate des Zugs, das gemacht werden soll
* @param y Y-Koordinate des Zugs, das gemacht werden soll
* @param colorToSet Farbe des Steins, der gesetzt werden soll
* @param directions Alle Richtungen in denen gedreht werden kann
*/
public void setWithUndo (byte x, byte y, byte colorToSet,
byte [][] directions)
{
int
numberDeletedPositions;
byte [][] deletedPositions;
byte [][] deletedPostionsWithColor;
set (x, y, colorToSet, directions);
deletedPositions
= getCoordinatesDeleted ();
numberDeletedPositions
= deletedPositions.length;
deletedPostionsWithColor = new byte [numberDeletedPositions][3];
for (byte i = 0; i < numberDeletedPositions; i++)
{
deletedPostionsWithColor [i] = new byte [] {
deletedPositions [i][0],
deletedPositions [i][1],
(colorToSet == Board.BLACK_PLAYER)
? Board.WHITE_PLAYER : Board.BLACK_PLAYER};
};
/* Speicher den gemachten Zug auf dem Stapel, damit er rueckgaengig
* gemacht werden kann
*/
setStack.push (new byte [] {x, y});
/* Speichere alle Felder, die geschlagen wurden und in die
* gegnerische Farbe gedreht wurden
*/
setStack.push (deletedPostionsWithColor);
}
}
Intelligence.java
package ai;
import reversi.Board;
import java.util.Random;
Seite 43 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
/**
* Abstrakte Basisklasse fuer alle kuenstlichen Intelligenzen.
* Stellt die Methode getBoardEvaluation bereit, die die abgeleiteten
* Intelligenzklassen benoetigen, um die Wertigkeit eines Zuges
* einzuschaetzen.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;Intelligence.java<br>
* <b>Date:</b>08.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
public abstract class Intelligence
{
/**
* Der beste Zug, den die jeweilige Intelligenzklasse je nach
* Schwierigkeitsstufe machen wird.
*/
protected Move
bestMove;
/**
* Dient zum Generieren der Zuege, die bei einer gegebenen
* Spielsituation gemacht werden koennen.
*/
protected MoveGenerator moveGenerator;
/**
* Dient zum Generieren einer Zufallszahl, die benutzt wird, um aus
* einer gegebenen Menge von Zuegen eine zufaellig auszuwaehlen.
*/
protected Random
numberGenerator;
/**
* Gibt einen Wert pro Feld auf dem Spielfeld an, der die Wertigkeit
* des jeweiligen Feldes wiedergibt, damit die Intelligenzklasse
* Entscheidungskriterien zum Setzen eines Steines hat.
* Ein Stein in den vier Ecken kann waehrend des Spielverlaufs nicht
* gedreht werden und erhaelt daher den hoechsten Wert von 1000.
* Den niedrigsten Wert von 5 erhalten die vier Felder, die von allen
* acht Seiten gedreht werden koennten und ueber den es moeglich ist,
* dass der Gegner einen Stein in den Ecken setzen kann. Die Felder,
* die sich an den Raendern des Spielfeldes befinden und ueber den der
* Gegner einen Stein in die Ecke setzen kann, erhalten einen etwas
* hoeheren Wert mit 10, da der Gegner nur zwei Moeglichkeiten hat,
* diesen Stein zu drehen. Einen Wert von 300 bekommen die Felder am
* Rand des Spielfeldes, die der Gegner somit nur aus zwei moeglichen
* Position schlagen kann. Jedoch kann der vom Gegner gesetzte Stein
* neben ein 300er-Feld niemals in eine Eckposition gelangen, kann
* aber dazu fuehren, dass der Gegner den Stein auf ein 10er-Feld
* setzt und somit dem Spieler ermoeglicht, danach die Chance zum
* Setzen auf ein 1000er-Feld zu erlangen. Die anderen Werte ergeben
* sich analog aus der Ueberlegung heraus, dass eine Position einen
* niedrigeren Wert erhaelt falls der gegnerische Spieler nach dem
* eigenen Setzen auf diese Position eine Position mit einer hoeheren
* Bewertung erlangen kann und umgekehrt.
*/
private int [][]
evaluationBoard =
{{1000, 10, 300, 300, 10, 1000},
{ 10,
5, 100, 100,
5,
10},
{ 300, 100, 50, 50, 100, 300},
{ 300, 100, 50, 50, 100, 300},
{ 10,
5, 100, 100,
5,
10},
{1000, 10, 300, 300, 10, 1000}
Seite 44 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
};
/**
* Initialisiert einen MoveGenerator zum Erzeugen von moeglichen
* Zuegen, einen Zufallsgenerator sowie den besten Zug, der gemacht
* werden kann.
*/
protected Intelligence ()
{
moveGenerator
= new MoveGenerator ();
numberGenerator = new Random ();
bestMove
= null;
}
/**
* Sucht den an
*
* @param board
* @param color
*/
public abstract
die Schwierigkeitsstufe angepassten besten Zug.
Brett, das zum Suchen des besten Zuges benutzt wird
Farbe des Spielers fuer den der beste Zug gesucht wird
void searchForBestPosition (byte [][] board,
byte color);
/**
* Liefert die X-Koordinate des Zuges, den die KI entsprechend der
* Schwierigkeitsstufe berechnet hat
*
* @return X-Koordinate des von der KI berechneten naechsten Zuges
*/
public byte getBestPositionX ()
{
return bestMove.getPositionX ();
}
/**
* Liefert die Y-Koordinate des Zuges, den die KI entsprechend der
* Schwierigkeitsstufe berechnet hat
*
* @return Y-Koordinate des von der KI berechneten naechsten Zuges
*/
public byte getBestPositionY ()
{
return bestMove.getPositionY ();
}
/**
* Bewertungsfunktion, die das Spielbrett auf einen einzigen Wert
* abbildet. Wird gebraucht, um zu entscheiden ob sich nach einem
* Zug der weisse Spieler oder der schwarze Spieler im Vorteil
* befindet oder das Spiel ausgeglichen ist.
*
* @param board Spielbrett, fuer die Brettbewertung berechnet wird
* @return Bewertung des Spielbretts board
*/
public int getBoardEvaluation (byte [][] board)
{
int evaluation = 0;
for (byte i = 0; i < Board.MAX_X; i++)
{
for (byte j = 0; j < Board.MAX_Y; j++)
Seite 45 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
{
if (board [i][j] != Board.EMPTY)
{
evaluation += board [i][j] * evaluationBoard [i][j];
};
};
};
return evaluation;
}
/**
* Liefert alle Richtungen, in denen Steine einer bestimmten Farbe
* nach dem Setzen eines Steins auf einer Position gedreht werden
* muessen
*
* @return Richtungen, in denen Steine einer Farbe gedreht werden
*/
public byte [][] getValidDirections ()
{
return bestMove.getValidDirections ();
}
}
IntelligenceEasy.java
package ai;
/**
* Berechnet einen Zug, der an eine leichte Schwierigkeitsstufe
* angepasst ist. Der zu machende Zug ist ein Zug, der aus der Menge der
* Zuege, die zu einer gegebenen Spielsituation gemacht werden koennen
* einen zufaellig aussucht und setzt.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;IntelligenceEasy.java<br>
* <b>Date:</b>08.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
public class IntelligenceEasy extends Intelligence
{
/**
* Initialisiert alle Variablen durch Aufruf des Konstruktors seines
* Vaters
*/
public IntelligenceEasy ()
{
super ();
}
/**
* Waehlt aus den von MoveGenerator gelieferten Zuegen zu einer
* gegebenen Spielsituation einen zufaellig aus.
*
* @param board Brett, das zum Suchen des besten Zuges benutzt wird
* @param color Farbe des Spielers fuer den der beste Zug gesucht wird
*/
public void searchForBestPosition (byte [][] board, byte color)
{
moveGenerator.checkForMoves (board, color);
bestMove = moveGenerator.getMove (
Seite 46 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
numberGenerator.nextInt (moveGenerator.getMovesSize ()));
}
}
IntelligenceHard.java
package ai;
import reversi.Board;
import reversi.Rules;
/**
* Schwierigste Stufe der kuenstlichen Intelligenz, gegen den ein
* menschlicher Spieler spielen kann. Zum Suchen des Zuges wird
* der Alpha-Beta-Algorithmus benutzt.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;IntelligenceHard.java<br>
* <b>Date:</b>08.11.2006<br>
* <b>History:</b><br>
*
09.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Implementierung des Alpha-Beta Algorithmus<br>
*
* @author Mehmet Akin
* @version 1.1
*/
public class IntelligenceHard extends Intelligence
{
/**
* Alpha-Wert der Alpha-Beta-Suche. Der weisse Spieler versucht diesen
* Wert zu maximieren, der schwarze so niedrig wie möglich zu halten
*/
private static final int ALPHA
= -99999;
/**
* Beta-Wert der Alpha-Beta-Suche.Der schwarze Spieler versucht diesen
* Wert zu maximieren, der weisse so niedrig wie möglich zu halten
*/
private static final int BETA
= 99999;
/**
* Anzahl von Zuegen, die im Voraus simuliert werden sollen. Bei einer
* Tiefe von drei hat ein menschlicher Spiele noch Chancen gegen die
* kuenstliche Intelligenz zu gewinnen, ist aber sehr schwer.
*/
private static final byte MAX_DEPTH = 3;
/**
* Bewertung des Spielfeldes, dass von getValueForGameEnd zurueck
* gegeben wird, wenn das Spiel waehrend der Zugsimulation beendet
* werden kann. Sobald der weisse Spieler einen Zug auswaehlen kann,
* dessen resultierendes Brett diese Bewertung hat, wird er diesen Zug
* nehmen, weil er Gewinngarantie hat. Der schwarze Spieler, der einen
* moeglichst geringen Wert erreichen will, wird daher den Zug, der
* zu diesem Wert fuehrt auf keinen Fall nehmen.
*/
private static final int WIN
= 10000000;
/**
* Bewertung des Spielfeldes, dass von getValueForGameEnd zurueck
* gegeben wird, wenn das Spiel waehrend der Zugsimulation beendet
* werden kann. Sobald der schwarze Spieler einen Zug auswaehlen kann,
* dessen resultierendes Brett diese Bewertung hat, wird er diesen Zug
* nehmen, weil er Gewinngarantie hat. Der weisse Spieler, der einen
* moeglichst hohen Wert erreichen will, wird daher den Zug, der
* zu diesem Wert fuehrt auf keinen Fall nehmen.
Seite 47 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
private static final int LOSE
= -10000000;
/**
* Bewertung des Spielfeldes, dass von getValueForGameEnd zurueck
* gegeben wird, wenn das Spiel waehrend der Zugsimulation zu einem
* Unentschieden zwischen den Spielern fuehrt.
*/
private static final int DRAW
= 0;
/**
* Brett zum Simulieren von Zuegen.
*/
private BoardSimulator
boardSimulator;
/**
* Regeln nach den das Spiel gespielt wird.
*/
private Rules
rules;
/**
* Initilisiert einen BoardSimulator und erzeugt Rules, die
* entscheiden ob ein Zug gueltig ist.
*/
public IntelligenceHard ()
{
super ();
boardSimulator = new BoardSimulator ();
rules
= new Rules ();
}
/**
* Startet die Funktion alphaBeta zum Suchen des besten Zuges.
*
* @param board Brett, das zum Suchen des besten Zuges benutzt wird
* @param color Farbe des Spielers fuer den der beste Zug gesucht wird
*/
public void searchForBestPosition (byte [][] board, byte color)
{
boardSimulator.setBoard (board);
alphaBeta (color, (byte) 0, ALPHA, BETA);
}
/**
* Implementierung des Alpha-Beta-Algorithmus. Der
* Alpha-Beta-Algorithmus ist ein Algorithmus zur Bestimmung des
* besten Spielzuges bei Spielen, bei denen zwei Spieler abwechselnd
* Zuege ausfuehren muessen. Beim rekursiven Aufbau des Baumes werden
* jeweils zwei Werte Alpha und Beta aktualisiert, ueber die es
* moeglich ist zu entscheiden, ob ein Teilbaum nicht mehr berechnet
* werden muss, weil er fuer das Endergebnis keine Rolle spielt. Die
* Grundidee des Algorithmus besteht darin, dass der weisse Spieler
* versucht, den Wert, den er bei optimaler Spielweise des schwarzen
* Spielers mindestens erreichen kann, zu maximieren und umgekehrt.
* Der Alpha-Wert, den der weisse Spieler versucht zu maximieren,
* wird mit –inf (minus unendlich) und der Beta-Wert, den der schwarze
* Spieler versucht zu minimieren, wird mit +inf (unendlich)
* initialisiert. Wenn ein Knoten, indem maximiert wird einen Folgezug
* besitzt, dessen Wert groesser als der Beta-Wert ist, so wird die
* Suche fuer diesen Knoten abgebrochen (Beta-Cutoff), weil das
* Ergebnis ein zu gutes Ergebnis fuer den gegnerischen Spieler
* liefern wuerde. Ist der Wert des Zuges kleiner als Beta und
* groesser als Alpha, so wird dieser Zug nach oben weiter gereicht.
Seite 48 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* Umgekehrt gilt fuer den minimierenden Spieler, dass bei einem Wert
* abgebrochen wird, der kleiner als Alpha ist (Alpha-Cutoff) und
* Beta an den Wert des Zuges angepasst wird, falls dieser groesser
* als Alpha und kleiner als Beta ist.
*
* @param color Farbe des Spielers fuer den der beste Zug gesucht wird
* @param depth aktuelle Tiefe, fuer die die Folgezuege berechnet wird
* @param alpha Alpha-Wert des weissen Spielers
* @param beta Beta-Wert des schwarzen Spielers
* @return Bewertung des Knotens, fuer den die Folgezuege berechnet
*
werden, je nachdem ob ein maximierender Knoten an der Reihe
*
ist oder ein minimierender
*/
private int alphaBeta (byte color, int depth, int alpha, int beta)
{
int
currentValue;
int
bestValue;
MoveGenerator moveGen;
byte
otherColor = (color == Board.WHITE_PLAYER)
? Board.BLACK_PLAYER
: Board.WHITE_PLAYER;
/* Wenn die maximale Tiefe erreicht wird, hoert die Suche auf und
* der Wert des Blatts wird zurueck gegeben
*/
if (depth == MAX_DEPTH)
{
return getBoardEvaluation (boardSimulator.getBoard ());
};
if (rules.hasToStay (boardSimulator.getBoard (), color))
{
if (rules.hasToStay (boardSimulator.getBoard (), otherColor))
{
return getValueForGameEnd (boardSimulator.getBoard ());
}
else
{
return alphaBeta (otherColor, depth - 1, alpha, beta);
};
};
moveGen = new MoveGenerator ();
moveGen.checkForMoves (boardSimulator.getBoard (), color);
if (color == Board.WHITE_PLAYER)
{
/* Der Wert, den der weisse Spieler versucht zu maximieren
bestValue = -9999999;
}
else
{
/* Der Wert, den der schwarze Spieler versucht zu minimieren
bestValue = 9999999;
};
*/
*/
/* Pruefe fuer alle moeglichen Zuege, die in einer gegebenen
* Brettstellung moeglich sind
*/
while (moveGen.hasMore ())
{
Seite 49 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Move move = moveGen.getNextMove ();
boardSimulator.setWithUndo (move.getPositionX (),
move.getPositionY (), color,
move.getValidDirections ());
/* Gehe eine Tiefe runter im Suchbaum und suche dort weiter
currentValue = alphaBeta (otherColor, depth + 1, alpha, beta);
boardSimulator.undoLastSet ();
*/
if (color == Board.WHITE_PLAYER)
{
/* Fuer den weissen Spieler ist der Zug nur interessant, falls
* sein aktueller Alpha-Wert kleiner als der Wert des aktuellen
* Zuges ist
*/
if (currentValue > bestValue)
{
bestValue = currentValue;
if (depth == 0)
{
bestMove = move;
};
/* Wenn der Wert des aktuellen Zuges besser als der Beta Wert
* ist hoert die Suche auf, weil das Ergebnis ein zu gutes
* fuer den schwarzen Spieler waere
*/
if (currentValue >= beta)
{
return currentValue;
};
if (currentValue > alpha)
{
alpha = currentValue;
};
};
}
else
{
/* Fuer den schwarzen Spieler ist der Zug nur interessant, falls
* sein aktueller Beta-Wert groesser als der Wert des aktuellen
* Zuges ist
*/
if (currentValue < bestValue)
{
bestValue = currentValue;
if (depth == 0)
{
bestMove = move;
};
/* Wenn der Wert des aktuellen Zuges besser als der Aplha-Wert
* ist hoert die Suche auf, weil das Ergebnis ein zu gutes
* fuer den schwarzen Spieler waere
*/
if (currentValue <= alpha)
{
return currentValue;
}
/* Ansonsten wird der neue beste Wert im Baum nach oben
* weiter gereicht
*/
Seite 50 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
if (currentValue < beta)
{
beta = currentValue;
};
};
};
};
return bestValue;
}
/**
* Bewertet das Spielbrett bei Spielende. Bei Gewinn fuer weiss wird
* ein sehr hoher Wert zurueck gegeben, bei Gewinn fuer schwarz ein
* sehr niedriger, bei Unentschieden wird 0 zurueck gegeben.
*
* @param board Brett, fuer das die Endbewertung berechnet werden soll
* @return Bewertung des Spielbretts bei Spielende
*/
private int getValueForGameEnd (byte [][] board)
{
byte numberWhiteStones = 0;
byte numberBlackStones = 0;
int difference;
for (byte i = 0; i < Board.MAX_X; i++)
{
for (byte j = 0; j < Board.MAX_Y; j++)
{
if (board [i][j] == Board.WHITE_PLAYER)
{
numberWhiteStones++;
};
if (board [i][j] == Board.BLACK_PLAYER)
{
numberBlackStones++;
};
};
};
difference = numberWhiteStones - numberBlackStones;
if (difference > 0)
{
return WIN;
}
else if (difference < 0)
{
return LOSE;
}
else
{
return DRAW;
};
}
}
IntelligenceNormal.java
package ai;
Seite 51 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
import java.util.Stack;
/**
* Berechnet einen Zug, der an eine normale Schwierigkeitsstufe
* angepasst ist. Es wird der Zug gesetzt, der den Spieler der
* jeweiligen Farbe (die KI) zu einer vorteilhaften Spielsituation
* fuehrt. Es wird nur ein Zug im Voraus berechnet.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;IntelligenceNormal.java<br>
* <b>Date:</b>08.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
public class IntelligenceNormal extends Intelligence
{
/**
* Spielbrett, auf dem ein Zug simuliert wird.
*/
private BoardSimulator boardSimulator;
/**
* Initialiesiert das Spielbrett, dass zum Simulieren von Zuegen
* benutzt wird.
*/
public IntelligenceNormal ()
{
super ();
boardSimulator = new BoardSimulator ();
}
/**
* Waehlt aus den von MoveGenerator gelieferten Zuegen, den aus, der
* zu einer Spielsituation fuehrt, in dem der Spieler, der gerade
* setzen muss, im Vorteil ist. Gibt es mehrere gleichwertige Zuege,
* so wird aus der Menge ein Zug zufaellig ausgewaehlt.
*
* @param board Brett, das zum Suchen des besten Zuges benutzt wird
* @param color Farbe des Spielers fuer den der beste Zug gesucht wird
*/
public void searchForBestPosition (byte [][] board, byte color)
{
Move tempMove;
int valueForMove = -9999;
int currentValueForMove;
Stack possibleMoves = new Stack ();
boardSimulator.setBoard (board);
moveGenerator.checkForMoves (board, color);
while (moveGenerator.hasMore ())
{
tempMove = moveGenerator.getNextMove ();
boardSimulator.setWithUndo (tempMove.getPositionX (),
tempMove.getPositionY (), color,
tempMove.getValidDirections ());
currentValueForMove = color * getBoardEvaluation (board);
if (currentValueForMove > valueForMove)
{
Seite 52 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
possibleMoves.removeAllElements ();
valueForMove = currentValueForMove;
possibleMoves.push (tempMove);
}
else if (currentValueForMove == valueForMove)
{
possibleMoves.push (tempMove);
}
else
{
};
boardSimulator.undoLastSet ();
};
bestMove = (Move) possibleMoves.elementAt (
numberGenerator.nextInt (possibleMoves.size ()));
}
}
Move.java
package ai;
/**
* Ein Zug, der aus einer X- und einer Y-Koordinate besteht.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;Move.java<br>
* <b>Date:</b>08.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
class Move
{
/**
* X-Koordinate des Zuges
*/
private byte
positionX;
/**
* Y-Koordinate des Zuges
*/
private byte
positionY;
/**
* Alle Richtungen, in denen Steine gedreht werden, wenn dieser Zug
* ausgefuehrt wird. Diese Information wird von den Intelligenzklassen
* gebraucht, um nicht unnoetige Berechnungen durchfuehren zu muessen
* wenn ein Zug rueckgaengig gemacht werden soll.
*/
private byte [][] validDirections;
/**
* Initialisiert den Zug.
*
* @param positionX X-Koordinate des Zuges
* @param positionY Y-Koordinate des Zuges
* @param validDirections Richtungen, in denen Steine gedreht werden
*/
public Move (byte positionX, byte positionY,
byte [][] validDirections)
{
this.positionX
= positionX;
this.positionY
= positionY;
this.validDirections = validDirections;
Seite 53 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
}
/**
* Liefert die X-Koordinate des Zuges.
*
* @return X-Koordinate des Zuges
*/
public byte getPositionX ()
{
return positionX;
}
/**
* Liefert die Y-Koordinate des Zuges.
*
* @return Y-Koordinate des Zuges
*/
public byte getPositionY ()
{
return positionY;
}
/**
* Liefer alle Richtungen, in denen Steine gedreht werden, wenn dieser
* Zug ausgefuehrt wird.
*
* @return Alle Richtungen, in denen Steine beim Setzen dieses Zugs
*
gedreht werden
*/
public byte [][] getValidDirections ()
{
return validDirections;
}
/**
* Setzt die X-Koordinate des Zuges.
*
* @param positionX X-Koordinate des Zuges
*/
public void setPositionX (byte positionX)
{
this.positionX = positionX;
}
/**
* Setzt die Y-Koordinate des Zuges.
*
* @param positionY Y-Koordinate des Zuges
*/
public void setPositionY (byte positionY)
{
this.positionY = positionY;
}
}
MoveGenerator.java
package ai;
import reversi.Board;
import reversi.Rules;
Seite 54 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
import java.util.Stack;
/**
* Berechnet die zu einer gegebenen Spielsituation alle moeglichen
* Zuege, die fuer die entsprechend ausgewaehlte Farbe auf dem aktuellen
* Spielbrett gesetzt werden koennen.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;MoveGenerator.java<br>
* <b>Date:</b>08.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
class MoveGenerator
{
/**
* Reversi-Regeln, die eine Gueltigkeit eines Zuges ueberpruefen.
*/
private Rules rules;
/**
* Stack zum Speichern von Zuegen, die simuliert werden, um zu
* ermoeglichen, dass ein Zug rueckgaengig gemacht werden kann.
*/
private Stack moves;
/**
* Initialisiert den Stack zum Speichern von simulierten Zuegen und
* die Reversi-Regeln, die fuer dieses Spiel benutzt werden.
*/
public MoveGenerator ()
{
moves = new Stack ();
rules = new Rules ();
}
/**
* Berechnet alle moeglichen Zuege, die ein Spieler einer Farbe zu
* einer gegebenen Spielsituation auf dem Spielbrett setzen kann.
*
* @param board Spielbrett, auf dem alle moeglichen Zuege
*
gesucht werden
* @param colorToCheck Farbe des Spielers fuer den alle moeglichen
*
Zuege gesucht werden sollen
*/
public void checkForMoves (byte [][] board, byte colorToCheck)
{
byte
colorNotToCheck = (colorToCheck == Board.WHITE_PLAYER)
? Board.BLACK_PLAYER
: Board.WHITE_PLAYER;
byte
color;
boolean [][] checkedBoard
= new boolean [6][6];
int
directionsLength = Board.DIRECTIONS.length;
byte []
direction;
moves = new Stack ();
for (byte i = 0; i < Board.MAX_Y; i++)
{
for (byte j = 0; j < Board.MAX_X; j++)
{
color = board [i][j];
Seite 55 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
if (color == colorNotToCheck)
{
for (byte k = 0; k < directionsLength; k++)
{
direction = Board.DIRECTIONS [k];
byte tempX = (byte) (i + direction [0]);
byte tempY = (byte) (j + direction [1]);
if ((!Board.isPositionOutOfBounds (tempX, tempY)) &&
(board [tempX][tempY] == Board.EMPTY) &&
(checkedBoard [tempX][tempY] == false))
{
checkedBoard [tempX][tempY] = true;
byte [][] validDirections = rules.getValidDirections (
board, tempX, tempY,
colorToCheck);
if (validDirections.length > 0)
{
moves.push (new Move (tempX, tempY, validDirections));
};
};
};
};
};
};
}
/**
* Liefert den Zug an der Stelle position im Stack zum Speichern der
* Zuege.
*
* @param position Stackindex, an dessen Stelle sich der gewuenschte
*
Zug befindet.
* @return Zug aus dem Stack mit dem Index position
*/
public Move getMove (int position)
{
return (Move) moves.elementAt (position);
}
/**
* Liefert die Anzahl der Zuege die sich im Zugstack befinden.
*
* @return Anzahl der Zuege im Zugstack
*/
public int getMovesSize ()
{
return moves.size ();
}
/**
* Liefert den naechsten Zug aus dem Stack ueber pop.
*
* @return Zug, der im Stack als letztes eingefuegt wurde
*/
public Move getNextMove ()
{
return (Move) moves.pop ();
Seite 56 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
}
/**
* Gibt an, ob im Zugstack noch Zuege verfuegbar sind.
*
* @return true wenn noch mindestens ein Zug im Stack ist, ansonsten
*
false
*/
public boolean hasMore ()
{
return !moves.empty ();
}
}
Paket main
CanvasListener.java
package main;
/**
* Schnittstelle fuer alle Klassen, die auf Betaetigungen der Handy* Tastatur reagieren. die Klasse GameController leitet den Wert,
* der Taste, due gedrueckt wird an die Klassen weiter, die diese
* Schnittstelle implementieren.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;CanvasListener.java<br>
* <b>Date:</b>21.10.2006<br>
* @author Mehmet Akin
* @version 1.0
*/
public interface CanvasListener
{
/**
* Dient dazu, der implementierenden Klasse auf die Betaetigung einer
* Taste des Handys zu reagieren.
*
* @param gameAction
*/
public void reactOnKeyPressed (int gameAction);
/**
* Fuehrt dazu, dass die Klasse, die sich im Hintergrund befindet
* diejenige ist, die Zugriff auf die Anzeige erhaelt und somit
* reaktiviert wird.
*/
public void reactivate ();
/**
* Dient dazu, dass die entsprechende Klasse zum ersten Mal auf dem
* Bildschirm angezeigt wird, wenn sie Zugriff auf die Anzeige erhaelt
*/
public void start ();
}
Game.java
package main;
Seite 57 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
import graphics3d.Board3D;
import graphics3d.MessageCreator;
import player.Player;
import player.PlayerHuman;
import player.PlayerNetwork;
import reversi.Reversi;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.m3g.Sprite3D;
/**
* Das Spiel. Ein Spiel hat zwei Spieler und Regeln, die es steuern
* muss. Das Spiel wird alleine durch Betaetigungen der Spieltasten
* asynchron gesteuert. Je nachdem, welcher Spieler an der Reihe ist,
* wird ein Tastendruck an diesen Spieler weiter geleitet. Weiterhin
* zeigt es Spielnachrichten am Spielanfang- und -ende an.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;Game.java<br>
* <b>Date:</b>21.10.2006<br>
* <b>History:</b><br>
*
28.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Sounds werden nun abgespielt, wenn das Spielt beginnt oder
*
endet, wenn die Plane bewegt wird, wenn ein Stein gesetzt
*
wird sowie ein Spieler ein ungueltiges Feld auswaehlt<br>
*
26.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Am Anfang und Ende des Spiels werden nun Nachrichten
*
angezeigt, die anzeigen, wer das Spiel anfaengt und wer am
*
Ende gewonnen hat<br>
*
25.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Es ist nun moeglich vom Spiel zurueck ins Hauptmenue zurueck
*
zu kehren<br>
*
* @author Mehmet Akin
* @version 1.3
*/
public class Game implements CanvasListener
{
/**
* 3D-Representation des internen Spielbretts
* */
private Board3D
board3D;
/**
* Spielsteuerung, um Tastenbetaetigungen weiterzuleiten
*/
private GameController controller;
/**
* Spieler, der aktuell an der Reihe ist, einen Zug zu machen
*/
private Player
currentPlayer;
/**
* Spielnachricht, die am Ende des Spiels angezeigt wird
*/
private Sprite3D
gameEndMessage;
/**
* Spielnachricht, die am Beginn des Spiels angezeigt wird
*/
private Sprite3D
gameStartMessage;
/**
* Wenn das Spiel zu Ende ist, wird es auf true gesetzt
*/
private boolean
isGameOver;
/**
* Der Spieler, der nicht an der Reihe ist, einen Zug zu machen
Seite 58 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
private Player
otherPlayer;
/**
* Spieler, der den allerersten Zug bei Spielanfang macht
*/
private Player
playerOne;
/**
* Spieler, der den zweiten Zug bei Spielanfang macht
*/
private Player
playerTwo;
/**
* Interne Darstellung des Spielbretts mit den Spielregeln zum Setzen
*/
private Reversi
reversi;
/**
* Initialisiert das Spiel.
*
* @param playerOne Spieler, der den ersten Zug macht
* @param playerTwo Spieler, der den zweiten Zug bei Spielanfang macht
* @param controller Spielsteuerung zur Weiterleitung von
*
Tastenbetaetingungen
*/
public Game (Player playerOne, Player playerTwo,
final GameController controller)
{
this.playerOne = playerOne;
this.playerTwo = playerTwo;
this.controller = controller;
isGameOver
= false;
reversi
= new Reversi (controller);
board3D
= reversi.getBoard3D ();
currentPlayer
= playerOne;
otherPlayer
= playerTwo;
buildGameStartMessage ();
/* Um das Bild bei Programmbeginn anzuzeigen, muss dieses in einem
* separaten Thread gemacht werden, weil ansonsten das Bild erst
* zu sehen ist, nachdem die Nachricht entfernt worden ist
*/
new Thread (new Runnable ()
{
public void run ()
{
controller.getSoundPlayer ().playSoundStartGame ();
showGameStartMessage ();
}
}).start ();
}
/**
* Fuehrt dazu, dass der andere Spiele an Reihe ist mit Setzen
*/
public void changePlayer ()
{
board3D.changePlayer ();
Player tempPlayer = currentPlayer;
currentPlayer = otherPlayer;
Seite 59 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
otherPlayer
= tempPlayer;
if (currentPlayer.handlePlayerChanged ())
{
/* Der Spieler, der an der Reihe ist, soll nun wieder Zugriff
* auf das Spielbrett haben und ein Feld zum Setzen auswaehlen
* koennen
*/
resetCanvasListener ();
};
}
/**
* Wertet das Spielergebnis aus, wenn das Spiel zu Ende ist. Zeigt
* die entsprechende Nachricht, welcher Spieler gewonnen hat, an.
*/
public void evaluateGameEnd ()
{
isGameOver = true;
buildGameEndMessage ();
showGameEndMessage ();
}
/**
* Fuehrt dazu, dass der Spieler, der aktuell gesetzt hat, nochmal
* setzen darf, weil der Gegenspieler in der gegebenen Spielsituation
* nicht in der Lage ist zu Setzen
*/
public void handleOtherPlayerHasToStay ()
{
otherPlayer.notifyHasToStay ();
}
/**
* Fuehrt dazu, dass der aktuell setzende Spieler ein anderes
* Spielfeld zum Setzen auswaehlen muss, weil er in einem voran
* gegangenen Zug ein ungueltiges Feld ausgewaehlt hat.
*/
public void handleSetNotValid ()
{
resetCanvasListener ();
}
/**
* Reagiert auf Tastenbetaetigungen des menschlichen Spielers. Wenn
* der Spieler die Pfeiltasten drueckt, wird nur die Plane in die
* entsprechende x- oder y-Richtung verschoben, um das ausgewaehlte
* Spielfeld kennzuzeichnen. Wenn der Spieler die Feuertaste drueckt,
* dann wird entsprechend ausgewertet, ob der Spieler, der an der
* Reihe ist, auf das gewaehlte Feld setzen darf oder nicht.
*/
public void reactOnKeyPressed (int gameAction)
{
/* Wenn ein Spieler waehrend einem Spiel das Spiel vorzeit verlaesst
* so muss ueberprueft werden, ob es sich um ein Netwerkspiel
* handelt, um noetige Verbindungen zu beenden
*/
if (gameAction == -11)
{
if (playerOne instanceof PlayerNetwork)
Seite 60 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
{
((PlayerNetwork) playerOne).logout ();
}
else if (playerTwo instanceof PlayerNetwork)
{
((PlayerNetwork) playerTwo).logout ();
}
else
{
resetGame ();
};
};
if (!isGameOver)
{
if (gameAction == GameCanvas.UP)
{
reversi.moveUp ();
}
else if (gameAction == GameCanvas.DOWN)
{
reversi.moveDown ();
}
else if (gameAction == GameCanvas.LEFT)
{
reversi.moveLeft ();
}
else if (gameAction == GameCanvas.RIGHT)
{
reversi.moveRight ();
}
else if (gameAction == GameCanvas.FIRE)
{
evaluateFirepressed ();
}
else
{
System.err.println ("Unknown gameAction Error in class Game!");
};
};
}
/**
* @see CanvasListener#reactivate()
*/
public void reactivate ()
{
}
/**
* Fuehrt dazu, dass die Plane, dei anzeigt, welches Spielfeld
* aktuell ausgewaehlt ist, wieder verschoben werden kann.
*/
public void resetCanvasListener ()
{
controller.setDisplayable3D (this);
}
/**
* Fuehrt dazu, dass das Hauptmenue des Spiels wieder angezeigt wird.
*/
Seite 61 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
public void resetGame ()
{
controller.resetGame ();
}
/**
* Zeigt eine Spielnachricht bei Spielende an, die angibt welcher
* Spieler gewonnen hat.
*/
public void showGameEndMessage ()
{
board3D.showEndMessage (gameEndMessage);
}
/**
* Zeigt eine Spielnachricht bei Spielanfang aus, die angibt welcher
* der beiden Spieler anfaengt.
*/
public void showGameStartMessage ()
{
board3D.showStartMessage (gameStartMessage);
}
/**
* @see CanvasListener#start()
*/
public void start ()
{
}
/**
* Liefert eine Referenz auf die Spielsteuerung, die die
* Tastenbetaetingungen des Spielers an diese Klasse weiterleitet.
*
* @return Spielsteuerung
*/
public GameController getController ()
{
return controller;
}
/**
* Liefert eine Referenz auf die interne Darstellung des Spiels
* samt Spielbrett und Spielregeln
*
* @return Referenz auf interne Darstellung des Spiels
*/
public Reversi getReversi ()
{
return reversi;
}
/**
* Fuehrt dazu, dass der Spieler der aktuell gesetzt hat, keine
* weiteren Ereignisse durch Druecken einer Taste ausloesen kann,
* ausser das Spiel zu beenden, bevor der gegnerische Spieler gesetzt
* hat.
*/
public void setIdleStatus ()
{
controller.setDisplayable3D (controller.getIdleListener ());
Seite 62 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
}
/**
* Erzeugt die Nachricht, die bei Spielende angezeigt wird. Ausserdem
* spielt es eine Audio-Datei ab, je nachdem um was fuer einen
* Spieltyp es sich handelt und ob der Spieler gewonnen oder verloren
* hat.
*/
private void buildGameEndMessage ()
{
int number = reversi.getWinNumber ();
if (number == 0)
{
gameEndMessage = MessageCreator.getMessageTie ();
}
else if (number > 0)
{
if (playerOne instanceof PlayerHuman)
{
if (playerTwo instanceof PlayerHuman)
{
gameEndMessage = MessageCreator.getMessageWhiteWins ();
}
else
{
controller.getSoundPlayer ().playSoundEndGame (true);
gameEndMessage = MessageCreator.getMessageYouWin ();
};
}
else
{
controller.getSoundPlayer ().playSoundEndGame (false);
gameEndMessage = MessageCreator.getMessageYouLose ();
};
}
else
{
if (playerOne instanceof PlayerHuman)
{
if (playerTwo instanceof PlayerHuman)
{
gameEndMessage = MessageCreator.getMessageBlackWins ();
}
else
{
controller.getSoundPlayer ().playSoundEndGame (false);
gameEndMessage = MessageCreator.getMessageYouLose ();
};
}
else
{
controller.getSoundPlayer ().playSoundEndGame (true);
gameEndMessage = MessageCreator.getMessageYouWin ();
};
};
}
/**
* Erzeugt die Nachricht, die bei Spielanfang angezeigt wird, um
Seite 63 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* den Spielern zu verdeutlichen welcher von den beiden den ersten
* Zug macht.
*/
private void buildGameStartMessage ()
{
if (playerOne instanceof PlayerHuman)
{
if (playerTwo instanceof PlayerHuman)
{
gameStartMessage = MessageCreator.getMessageWhiteBegins ();
}
else
{
gameStartMessage = MessageCreator.getMessageYouBegin ();
};
}
else
{
gameStartMessage = MessageCreator.getMessageRemotePlayerBegins ();
};
}
/**
* Fuehrt dazu, dass der Spieler, der aktuell ein Feld zum Setzen
* gewaehlt hat, solange keine Ereignisse durch Tastendruecke
* ausloesen kann, bis fest gestellt wurde, ob es sich um einen
* gueltigen Zug hanndelt und wenn ja, bis der Gegenspieler seinen
* Zug gemacht hat. Durch Druecken der Return-Taste kann das Spiel
* jedoch immer abgebrochen werden.
*/
private void evaluateFirepressed ()
{
controller.setDisplayable3D (controller.getIdleListener ());
currentPlayer.doNextSet ();
}
}
GameController.java
package main;
import menu3d.Menu3DLoader;
import menu3d.MenuGameLevel;
import menu3d.MenuGameStart;
import menu3d.MenuGameType;
import player.Player;
import sound.SoundPlayer;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
/**
* Die Spielsteuerung. Die Spielsteuerung kapselt die
* Benutzerinteraktion in Form von Tastendruecken auf dem Handy.
* Entweder reagiert ein Menue-Objekt auf Tastendruecke, indem die
* entsprechenden Optionen ausgewaehlt werden oder ein Spiel das
* getstartet wurde. Der Controller leitet die Tastendruecke
* transparent an die jeweilige Klasse ueber die Schnittstelle
* CanvasListener weiter. Die Klasse bietet Methoden zum Zugriff auf
* die Anzeige sowie zum Festlegen welche Objekte aktiv Zugriff auf
* die Anzeige haben sollen.<br><br>
Seite 64 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*
* <b>File:</b>&nbsp;&nbsp;GameController.java<br>
* <b>Date:</b>21.10.2006<br>
* <b>History:</b><br>
*
28.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Controller liefert nun den SoundPlayer an anlle Klassen,
*
die Toene abspielen muessen<br>
*
25.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Spiel startet nun nicht direkt, muss stattdessen ueber das
*
3D-Menue gestartet werden<br>
*
24.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
3D-Menue integriert mit getMenue3DLoader<br>
*
06.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
getIdleListener-Funktion hinzugefuegt<br>
*
05.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Erweitern der Set-Methode zum Rendering eines Steines<br>
*
* @author Mehmet Akin
* @version 1.5
*/
public class GameController extends GameCanvas
{
/**
* Tastenwert fuer die Return-Taste zum Zurueckkehren zum Hauptmenue
*/
public static final int BACK_TO_MENU = -11;
/**
* Menue zue Auswahl des Spieltyps
*/
private CanvasListener menuGameType;
/**
* Startmenue, das bei Programmstart angezeigt wird
*/
private CanvasListener menuGameStart;
/**
* Menue zur Auswahl der Schwierigkeitsstufe bei einem Spiel gegen
* das Handy
*/
private CanvasListener menuGameLevel;
/**
* Ladet alle 3D-Modelle, die fuer das Startmenue und alle anderen
* Untermenues gebraucht werden
*/
private Menu3DLoader
menu3DLoader;
/**
* Reagiert nur auf die Return-Taste. Wird gebraucht, wenn ein Spieler
* gesetzt hat und solange warten muss bis er wieder ein Feld waehlen
* kann bis der Gegenspieler auch gesetzt hat
*/
private CanvasListener idleListener;
/**
* Aktuell auf der Anzeige gerendertes CanvasListener-Objekt. Kann
* entweder ein Menue sein oder ein gestartetes Spiel
*/
private CanvasListener displayable3D;
/**
* Spielt Sounddateien ab
*/
private SoundPlayer
soundPlayer;
/**
* Anzeige des Handys. Zu einem Zeitpunkt kann immer nur ein Objekt
Seite 65 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* Zugriff auf die Anzeige haben
*/
private Display
display;
/**
* Gibt an, ob die Feuertaste gedrueckt wurde
*/
private boolean
firePressed;
/**
* Das Reversi-Spiel an sich, dass abwechselnd die Spieler die Zuege
* machen laesst und das Spielende auswertet
*/
private CanvasListener game;
/**
* Eistiegspunkt des Programm.
*/
private Main
main;
/**
* Initialisiert den GameController.
*
* @param main Programmeinstiegspunkt, beinhaltet die startApp-Methode
*/
public GameController (Main main)
{
super (false);
this.main
= main;
soundPlayer = new SoundPlayer ();
setFullScreenMode (true);
this.display = main.getDisplay ();
createMenu3DLoader ();
menuGameStart = new MenuGameStart (this);
((MenuGameStart) menuGameStart).showGameLoadDisplay ();
/* Wenn das Programm gestartet wird, soll ein Bild angezeigt werden,
* das den Namen des Spiels bekannt gibt. Jedoch muss dieses in
* einem separaten Thread geschehen, weil das innerhalb der
* startApp-Methode der MIDlet Klase aufgerufen wird. Auf der
* Anzeige kann das erste mal etwas angezeigt werden, wenn die
* startApp-Methode beendet wurde. Das bedeutet jedoch, dass das
* Bild nicht mehr angezeigt werden wuerde, wenn es im gleichen
* Thread laueft wie die startApp-Mehtode.
*/
new Thread (new Runnable ()
{
public void run ()
{
try
{
Thread.sleep (0);
} catch (InterruptedException e)
{
e.printStackTrace ();
};
setDisplayable3D (menuGameStart);
};
}).start ();
display.setCurrent (this);
}
/**
Seite 66 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* Gibt alle verbrauchten Ressourcen wieder frei und beendet das
* Programm.
*/
public void exitGame ()
{
main.die ();
}
/**
* Wertet den Wert der Taste aus, die gedrueckt wurde. Handelt es sich
* bei der Taste entweder um eine Pfeiltaste, Returntaste, Ok-Taste
* oder Back-Taste, dann wird der Wert an das displayable3D-Objekt
* weitergeleitet, ansonsten verworfen.
*
* @param keyCode Wert der Taste, die gedrueckt wurde
*/
public void keyPressed (int keyCode)
{
int gameAction = getGameAction (keyCode);
/* Beruecksichtige nur Tastenbetaetigungen fuer die Pfeiltasten,
* die Return-Taste, die Back-Taste sowie die Ok-Taste
*/
if ((gameAction == GameCanvas.UP) || (gameAction == DOWN) ||
(gameAction == RIGHT) || (gameAction == LEFT) ||
(gameAction == FIRE) || (keyCode == BACK_TO_MENU))
{
if (keyCode == BACK_TO_MENU)
{
gameAction = keyCode;
};
if (displayable3D != null)
{
displayable3D.reactOnKeyPressed (gameAction);
};
};
}
/**
* Setzt das Startmenue als aktuelle auf der Anzeige gerenderte Menue.
*/
public void resetGame ()
{
setDisplayable3D (getMenuGameStart ());
game = null;
}
/**
* Startet ein Spiel zwischen zwei Spielern und gibt alle Ressourcen
* frei, die fuer die Realisierung des 3D-Menues beoetigt wurden.
*
* @param playerOne Der Spieler, der den ersten Zug macht
* @param playerTwo der Spieler, der den zweiten Zug macht
*/
public void startGame (Player playerOne, Player playerTwo)
{
game = new Game (playerOne, playerTwo, this);
playerOne.setGame ((Game) game);
playerTwo.setGame ((Game) game);
Seite 67 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
setDisplayable3D (game);
/* Da das Spiel angefangen hat, muss im Hintergrund nicht Speicher
* fuer die Menues verschwendet werden, die nicht mehr benoetigt
* werden und die Ressourcen koennen freigegeben werden
*/
menu3DLoader = null;
menuGameLevel = null;
menuGameStart = null;
menuGameType = null;
}
/**
* Fuehrt dazu, dass die Feuertaste in einen Zustand geraet, in der
* Sie nicht gedrueckt ist.
*/
public void unsetFirePressed ()
{
firePressed = false;
}
/**
* Gibt an, ob die Feuertaste gedrueckt wurde.
*
* @return true, wenn Feuertaste gedrueckt wurde, ansonsten false
*/
public boolean getFirePressed ()
{
return firePressed;
}
/**
* Liefert das Spiel, dass die Spieler abwechselnd zum Ziehen
* benachrichtigt.
*
* @return Spiel zwischen zwei Spielern
*/
public Game getGame ()
{
return (Game)game;
}
/**
* Liefert den Zugriff auf die Zeichenflaeche der Anzeige.
*
* @return Graphics-Objekt zum Zugriff auf die Zeichenflaeche
*/
public Graphics getGraphicsInstance ()
{
return getGraphics ();
}
/**
* Liefert einen CanvasListener-Objekt, das nur auf das Druecken der
* Return-Taste reagiert.
*
* @return CanvasListener, das nur auf die Betaetigung der Return*
Taste reagiert
*/
public CanvasListener getIdleListener ()
{
return (idleListener == null)
Seite 68 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
? new CanvasListener ()
{
public void reactOnKeyPressed (int gameAction)
{
/* Reagiere nur, wenn der Benutzer das Spiel verlassen moechte*/
if (gameAction == -11)
{
game.reactOnKeyPressed (gameAction);
};
};
public void start ()
{
};
public void reactivate ()
{
};
}
: idleListener;
}
/**
* Liefert den Lader zum laden von 3D-Objekte fuer die Menueklassen.
*
* @return Lader zum Laden von 3D-Menueobjekten
*/
public Menu3DLoader getMenu3DLoader ()
{
return menu3DLoader;
}
/**
* Liefert das Menue zur Auswahl der Schwierigkeitsstufe. Falls schon
* eins erzeugt wurde, dann wird die alte Referenz zurueck gegeben,
* wenn nicht, dann wird ein neues Menue erzeugt.
*
* @return Menue zur Auswahl der Schwierigkeitsstufe bei einem Spiel
*
gegen das Handy
*/
public CanvasListener getMenuGameLevel ()
{
createMenu3DLoader ();
return (menuGameLevel == null)
? (menuGameLevel = new MenuGameLevel (this)) : menuGameLevel;
}
/**
* Liefert das Startmenue. Falls schon eins erzeugt wurde, dann wird
* die alte Referenz zurueck gegeben, wenn nicht, dann wird ein neues
* Menue erzeugt.
*
* @return Startmenue, das bei Programmstart angezeigt wird
*/
public CanvasListener getMenuGameStart ()
{
createMenu3DLoader ();
return (menuGameStart == null)
? (menuGameStart = new MenuGameStart (this)) : menuGameStart;
Seite 69 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
}
/**
* Liefert das Menue zur Auswahl des Spieltyps. Falls schon
* eins erzeugt wurde, dann wird die alte Referenz zurueck gegeben,
* wenn nicht, dann wird ein neues Menue erzeugt.
*
* @return Menue zur Auswahl des Spieltyps bei einem Spiel gegen das
*
Handy
*/
public CanvasListener getMenuGameType ()
{
createMenu3DLoader ();
return (menuGameType == null)
? (menuGameType = new MenuGameType (this)) : menuGameType;
}
/**
* Liefer den Audiospieler zum abspielen von Audiodateien bei
* gegebenen Spielsituationen.
*
* @return Spieler zum abspielen von Audiodateien
*/
public SoundPlayer getSoundPlayer ()
{
return soundPlayer;
}
/**
* Setzt das aktuell zuf der Anzeige zu rendernde Objekt.
*
* @param displayable Das auf der Anzeige zu rendernde Objekt
*/
public void setDisplay (Displayable displayable)
{
display.setCurrent (displayable);
}
/**
* Setzt den Wert der Variable displayable3D, an die die Werte der
* Tasten weiter geleitet werden, wenn sie gedrueckt werden.
*
* @param displayable3D Objekt, an das Tastendruecke weiter geleitet
*
werden
*/
public void setDisplayable3D (CanvasListener displayable3D)
{
this.displayable3D = displayable3D;
displayable3D.reactivate ();
displayable3D.start ();
}
/**
* Erzeugt ein neuen Menu3DLoader zum Laden von 3D-Menueobjekten,
* wenn dieser nicht schon erzeugt wurde.
*/
private void createMenu3DLoader ()
{
if (menu3DLoader == null)
Seite 70 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
{
menu3DLoader = new Menu3DLoader ();
};
}
}
Main.java
package main;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
/**
* Einsprungspunkt fuer das Programm.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;Main.java<br>
* <b>Date:</b>21.10.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
public class Main extends MIDlet
{
/**
* Anzeige, auf der grafische Elemente ausgegeben werden koennen
*/
private Display display;
/**
* Initialisiert die Anzeige des Handys
*/
public Main ()
{
display = Display.getDisplay (this);
}
/**
* Aendert das Objekt, das aktuell Zugriff auf die Anzeige hat. Zu
* einem Zeitpunkt kann immer nur ein Objekt Zugriff auf die Anzeige
* haben
*
* @param displayable Referenz, dass Zugriff auf die Anzeige erhalten
*
soll
*/
public void changeDisplay (Displayable displayable)
{
display.setCurrent (displayable);
}
/**
* Fuehrt dazu, dass alle Systemressourcen des Spiels frei gegeben
* werden, wenn es beendet wird.
*/
public void die ()
{
notifyDestroyed ();
}
/**
Seite 71 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* Liefert den Zugriff auf die Anzeige des Handys
*
* @return Zugriff auf die Anzeige
*/
public Display getDisplay ()
{
return display;
}
/**
* Wird zur Laufzeit vom Handymanager aufgerufen, wenn das Spiel
* unerwartet beendet wird.
*/
protected void destroyApp (boolean arg0)
throws MIDletStateChangeException
{
notifyDestroyed ();
}
/**
* Wird benoetigt, um das aktuell angezeigte Bild in den Hintergrund
* zu verschieben, falls andere Ereignisse auftreten, die Zugriff
* auf die Anzeige brauchen, wie ein einkommender Telefonanruf.
*/
protected void pauseApp ()
{
display.setCurrent (null);
}
/**
* Einsprungsfunktion des Programms. Equivalent zur main-Methode
* bei J2SE.
*/
protected void startApp () throws MIDletStateChangeException
{
new GameController (this);
}
}
Paket network
OpponentChooser.java
package network;
import main.GameController;
import player.Player;
import player.PlayerHuman;
import player.PlayerNetwork;
import reversi.Board;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.List;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.Ticker;
/**
* Stellt die Formulare dar, die benoetigt werden, um ein Netwerkspiel
* zu starten. Das erste ist das Login-Formular, in dem der Benutzer
Seite 72 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* zuerst seinen gewuenschten Namen, mit dem er sich einwaehlen moechte
* eintragen kann und sich anschliessend auf dem Server einwaehlen kann.
* Ist er erfolgreich eingewaehlt erscheint ein zweites Formular, das
* alle verfuegbaren Spieler anzeigt, die auf dem Server eingewaehlt
* sind und aus denen der Spieler einen anderen zu einem Spiel einladen
* kann. Zum Senden und Empfangen von Daten benutzt der OppponentChosser
* den PlayerConnector als Schnittstelle zum Server.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;OpponentChooser.java<br>
* <b>Date:</b>11.11.2006<br>
* <b>History:</b><br>
*
27.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Ein Spieler kann einen anderen zum Spiel einladen<br>
*
26.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Formular hinzu gefuegt, mit dem sich ein Spieler auf dem
*
Server einwaehlen kann<br>
*
25.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Netzwerkspiel kann gestartet werden<br>
*
12.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Liste mit verfuegbaren Spielern auf dem Server kann angezeigt
*
werden<br>
*
* @author Mehmet Akin
* @version 1.4
*/
public class OpponentChooser implements CommandListener
{
/**
* Wird angezeigt, wenn kein Spieler verfuegbar ist
*/
private static String
NO_PLAYERS_ONLINE = "No players online!";
/**
* Dient dazu, zurueck zum Spieltypauswahlmenue zurueck zu kehren
*/
private Command
backToMenu;
/**
* Sendet eine Spieleinladung zu dem ausgewaehlten Spieler aus der
* Liste
*/
private Command
connect;
/**
* Wird angezeigt sobald der Spieler connect gewaehlt hat. Zeigt
* an, dass gerade versucht wird eine Verbindung mit dem gewaehlten
* entfernten Spieler herzustellen
*/
private Form
connectingForm;
/**
* Schnittstelle zum Server, dass das Senden und Empfangen von Daten
* erlaubt
*/
private PlayerConnector connector;
/**
* Spielsteuerung, dass Tastenbetaetigungen zum OpponenntChooser
* weiter leitet
*/
private GameController controller;
/**
* Gibt an, ob eine Verbinung zum Server hergestellt ist
*/
private boolean
isConnectedToServer;
/**
Seite 73 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* Gibt an, ob der Spieler derjenige ist, der den Gegenspieler
* eingeladen hat oder ob der Spieler eingeladen ist
*/
private boolean
isRequester;
/**
* Schickt eine Verbindungsanfrage zum Server
*/
private Command
login;
/**
* Einwahlformular zum Eintragen des gewuenschten Benutzernamens auf dem
* Server, der Serveradresse sowie des Ports auf dem der Server laeuft
* Bietet die beiden Funktionen Connect und Back. Mit Connect wird
* versucht eine Verbindung zum Server aufzubauen. Mit Back wird
* zurueck zum Spieltypauswahlmenue gewechselt
*/
private Form
loginForm;
/**
* Textfeld im Einwahlformular zum Eintragen des gewuenschten
* Benutzernamens
*/
private TextField
loginName;
/**
* Auswahloption, wenn ein eingeladener Spieler das Spiel nicht
* akzeptiert
*/
private Command
no;
/**
* Liste der verfuegbaren Spieler auf dem Server
*/
private List
playersList;
/**
* Portnummer, auf dem der Server laueft und zu dem die Verbindung
* aufgebaut werden soll
*/
private TextField
portNumber;
/**
* Server-URL zu dem die Verbindung aufgebaut werden soll
*/
private TextField
serverURL;
/**
* Auswahloption, wenn ein eingeladener Spieler das Spiel akzeptiert
*/
private Command
yes;
/**
* Initialisiert den OpponentChooser
*
* @param controller Spielsteuerung, die Tastenbetaetigungen des
*
Spielers an das Menue weiter leitet
*/
public OpponentChooser (GameController controller)
{
this.controller
= controller;
isRequester
= false;
isConnectedToServer = false;
login
= new Command ("Login", Command.SCREEN, 1);
backToMenu
= new Command ("Back", Command.SCREEN, 1);
yes
= new Command ("Yes", Command.SCREEN, 1);
no
= new Command ("No", Command.SCREEN, 1);
connect
= new Command ("Connect", Command.SCREEN, 1);
Seite 74 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
createLoginForm ();
controller.setDisplay (loginForm);
}
/**
* @see CommandListener#commandAction(Command, Displayable)
*/
public void commandAction (Command command, Displayable d)
{
if (command == login)
{
/* Starte den Login-Prozess als Thread, damit ein moegliches
* Blockieren des Clients verhindert wird, wenn auf Antworten
* aus den Netzwerk gewartet wird
*/
new Thread (new Runnable ()
{
public void run ()
{
evaluateLogin (loginName.getString ().trim (),
serverURL.getString ().trim (),
portNumber.getString ());
};
}).start ();
};
if (command == backToMenu)
{
if (isConnectedToServer)
{
isConnectedToServer = false;
connector.logout ();
};
returnToMenu ();
};
if (command == connect)
{
isRequester = true;
String opponentName = playersList.getString (
playersList.getSelectedIndex ());
if (!opponentName.equals (NO_PLAYERS_ONLINE))
{
connector.requestGame (opponentName);
createConnectingForm ();
};
};
if (command == yes)
{
connector.acceptGame ();
startNetworkGame ();
};
if (command == no)
{
connector.rejectGame ();
Seite 75 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
controller.setDisplay (playersList);
};
}
/**
* Wertet die Zeichenkette, der auf eine Login-Anfrage vom Server
* zurueck geschickt wird aus. Wenn die Zeichenkette leer ist, sind
* keine Spieler verfuegbar, wenn die Liste eine Null-Referenz ist,
* ist der gewuenschte Benutzername schon vergeben. Ansonsten enthaelt
* die Zeichenkette alle verfuegbaren Spielernamen getrennt durch
* ein Leerzeichen.
*
* @param list Liste der verfuegbaren Spieler auf dem Server
*/
public void evaluateListFromServer (String list)
{
createPlayersList ();
if (list == null)
{
loginForm.setTicker (new Ticker ("Your name is already " +
"used! Choose another one!"));
}
else if (list.equals (""))
{
playersList.append (NO_PLAYERS_ONLINE, null);
controller.setDisplay (playersList);
}
else
{
StringBuffer buffer = new StringBuffer ();
int
length = list.length ();
for (byte i = 0; i < length; i++)
{
char c = list.charAt (i);
if (c == ' ')
{
playersList.append (buffer.toString (), null);
buffer = new StringBuffer ();
}
else
{
buffer.append (list.charAt (i));
};
};
playersList.append (buffer.toString (), null);
/* Nachdem die Liste vom Server erhalten wurde, kann nun die
* Spielerliste auf der Anzeige gezeigt werden
*/
controller.setDisplay (playersList);
};
}
/**
* Reagiert auf einen Timeout, der ausgeloest wird, wenn ein Spieler
* einen anderen entfernten einladet, dieser Spieler jedoch nicht
* anwesend ist. Falls es sich um den einladenden handelt so
Seite 76 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* bekommt dieser die Nachricht angezeigt, dass das Spiel abgelehnt
* wurde. Bei dem eingeladenen bei dem ein Einladungsfenster
* erschienen ist, verschwindet dieses Fenster nach dem Timeout wieder
*/
public void reactOnTimout ()
{
if (isRequester)
{
playersList.setTicker (new Ticker ("Request timeout! The " +
"requested player is not available! " +
"Choose another player!"));
}
else
{
playersList.setTicker (new Ticker ("Request timeout! You " +
"were requested, but did not answer!"));
};
/* Wenn ein Timeout eingetroffen ist, muss wieder der Augangszustand
* des Clients hergestellt werden
*/
resetState ();
controller.setDisplay (playersList);
}
/**
* Nachdem ein Spiel angefangen hat oder ein Spiel abgelehnt wurde,
* kann die Variable, die angibt, ob der Spieler eingeladen hat oder
* eingeladen wurde wieder zurueck gesetzt werden.
*/
public void resetState ()
{
isRequester = false;
}
/**
* Falls ein Spieler eine Einladung bekommt, wird ein Fenster
* angezeigt, dass dies entsprechend anzeigt.
*
* @param requester Name des Spielers, von dem die Spielanfrage kommt
*/
public void responseToGameRequest (String requester)
{
if (!isRequester)
{
if (requester != null)
{
createGameRequestForm (requester);
};
};
}
/**
* Kehrt zurueck zum Spieltypauswahlmenue
*/
public void returnToMenu ()
{
controller.setDisplay (controller);
controller.setDisplayable3D (controller.getMenuGameType ());
}
Seite 77 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
/**
* Zeigt an, wenn eine Spieleinladung abgelehnt wurde.
*/
public void showGameRejected ()
{
resetState ();
playersList.append ("", null);
playersList.setTicker (new Ticker ("Game rejected! Please choose " +
"another player!"));
controller.setDisplay (playersList);
}
/**
* Startet ein Netwerkspiel zwischen zwei Spielern, wenn der
* eingeladene Spieler zugestimmt hat. Der einladende Spieler beginnt.
*/
public void startNetworkGame ()
{
if (isRequester)
{
/* Wenn es sich um den Spieler handelt, der den Gegner eingeladen
* hat, dann faengt dieser an. Der zweite Spieler simuliert
* lokal den entfernten Spieler
*/
Player playerOne = new PlayerHuman (Board.WHITE_PLAYER);
Player playerTwo = new PlayerNetwork (Board.BLACK_PLAYER,
connector);
connector.setPlayer ((PlayerNetwork) playerTwo);
controller.startGame (playerOne, playerTwo);
}
else
{
Player playerOne = new PlayerNetwork (Board.WHITE_PLAYER,
connector);
Player playerTwo = new PlayerHuman (Board.BLACK_PLAYER);
connector.setPlayer ((PlayerNetwork) playerOne);
controller.startGame (playerOne, playerTwo);
controller.getGame ().setIdleStatus ();
};
controller.setDisplay (controller);
connector.resetChooser ();
}
/**
* Liefert das Formular, auf dem sich der Spieler einwaehlen kann.
*
* @return Formular zum Einwaehlen auf dem Server
*/
public Form getForm ()
{
return loginForm;
}
/**
* Gibt an, ob eine Verbindung zum Server besteht.
*
* @return true wenn Verbindung zum Server besteht, ansonsten false
Seite 78 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
public boolean isConnectedToServer ()
{
return connector.isConnectedToServer ();
}
/**
* Gibt an, ob der Spieler einen anderen Spieler eingeladen hat oder
* eingeladen wurde
*
* @return true, wenn der Spieler eingeladen hat, ansonsten false
*/
public boolean isRequester ()
{
return isRequester;
}
/**
* Erzeugt ein Fenster, dass anzeigt, dass aktuell versucht wird, eine
* Verbindung zum gegnerischen Spieler aufzubauen.
*/
private void createConnectingForm ()
{
connectingForm = new Form ("Game request!");
connectingForm.append ("Your opponent is currently being " +
" requested!");
connectingForm.setTicker (new Ticker ("Connecting ..."));
controller.setDisplay (connectingForm);
}
/**
* Erzeugt ein Fenster, in dem der Spieler gefragt wird, ob er eine
* Einladung zu einem Spiel annehmen will oder nicht.
*
* @param requester Spielername, von dem die Einladung kommt
*/
private void createGameRequestForm (String requester)
{
Form form = new Form ("Game request!");
form.append ("Player " + requester + " is requesting for " +
"a game. Do you want to join ?");
form.addCommand (yes);
form.addCommand (no);
form.setCommandListener (this);
controller.setDisplay (form);
}
/**
* Erzeugt das Formular zum Einwaehlen auf dem Server. Das Formular
* besteht aus einem Textfeld zur Eintragung des gewuenschten
* Benuternamens, der Serveradresse und dem Port zu dem die
* Verbindung aufgebaut werden soll.
*/
private void createLoginForm ()
{
loginForm
loginName
= new Form ("Login screen!");
= new TextField ("Enter your name:", "", 10,
TextField.ANY);
Seite 79 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
serverURL
= new TextField ("Address of server:",
"125.23.21.64", 50,
TextField.ANY);
portNumber = new TextField ("Port number:", "1254", 50,
TextField.DECIMAL);
loginForm.append (loginName);
loginForm.append (serverURL);
loginForm.append (portNumber);
loginForm.addCommand (login);
loginForm.addCommand (backToMenu);
loginForm.setCommandListener (this);
loginForm.setTicker (new Ticker ("Enter your name and then push " +
"Login! Do not use blank fields!"));
}
/**
* Erzeugt das Fenster, in dem die verfuegbaren Spieler auf dem Server
* angezeigt werden koennen.
*/
private void createPlayersList ()
{
playersList = new List ("Available players!", List.IMPLICIT);
playersList.setSelectCommand (connect);
playersList.addCommand (backToMenu);
playersList.setCommandListener (this);
playersList.setTicker (new Ticker ("Hello " +
loginName.getString () +
"! Choose one of the players " +
"below and push Connect!"));
}
/**
* Wertet die eingegeben Daten aus, bevor diese an den Server gesendet
* werden. Falls der Benutzername leer ist, wird der Spieler
* aufgefordert mindestens ein Zeichen einzugeben. Falls die
* Verbindung zum Server nicht aufgebaut werden kann, so wird dies
* auch entsprechend in dem Fenster angezeigt.
*
* @param userName Benutzername, mit sich der Spieler
*
auf dem Server einwaehlen moechte
* @param connectUrl Adresse des Servers
* @param portNum Portnummer der Serveranwendung auf dem Server
*/
private void evaluateLogin (String userName, String connectUrl,
String portNum)
{
if (!userName.equals (""))
{
connector = new PlayerConnector (connectUrl, portNum);
if (connector.isConnectedToServer ())
{
isConnectedToServer = true;
connector.setOpponentChooser (this);
connector.login (userName);
}
else
{
Seite 80 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
loginForm.setTicker (new Ticker ("Server unreachable, please " +
"ensure right server address" +
"and port!"));
};
}
else
{
loginForm.setTicker (new Ticker ("You must enter at least one " +
"character!"));
};
}
}
PlayerConnector.java
package network;
import player.PlayerNetwork;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import javax.microedition.io.Connector;
import javax.microedition.io.SocketConnection;
/**
* Implementiert das Protokoll zur Kommunikation zwischen Client und
* Server. Stellt Funktionen zum Empfangen und Versenden von Daten
* von und zum Server bereit. Fuer den Verbindungsaufbau und -abbau
* wird der Connector vom OpponentChooser benutzt. Bei Spielanfang
* wird es vom PlayerNetwork direkt benutzt um Zuege zum gegnerischen
* Spieler zu versenden.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;PlayerConnector.java<br>
* <b>Date:</b>11.11.2006<br>
* <b>History:</b><br>
*
27.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Sieler kann nun eine Einladung ablehnen<br>
*
26.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Funktion closeConnection hinzu gefuegt<br>
*
13.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Mit dem Protokoll kann nun auch das serverseitige oder
*
spielerseitige Beenden des Spiels erkannt werden<br>
*
12.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
PlayerConnector kann mit PlayerLinker kommunizieren<br>
*
* @author Mehmet Akin
* @version 1.4
*/
public class PlayerConnector implements Runnable
{
/**
* Nachricht, die anzeigt, dass der gegnerische Spieler waehrend
* eines Netzwerkspiels das Spiel fruehzeitig beendet hat
*/
private static String
PLAYER_QUIT
= "PQ";
/**
* Anfrage des Clients, die Verbindung zum Server zu schliessen
*/
private static String
LOGOUT_REQ
= "RL";
/**
* Bestaetigung des Server auf eine Anfrage fuer den Verbindungsabbau
*/
Seite 81 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
private static String
LOGOUT_ACK
= "AL";
/**
* Anfrage des Clients, sich beim Server einzuwaehleb
*/
private static String
LOGIN_REQ
= "LI";
/**
* Ablehnung einer Verbindungsanfrage an den Server, wenn der
* Benutzername schon vergeben ist
*/
private static String
LOGIN_REJ
= "LJ";
/**
* Bestaetigung des Servers zu einer Verbindungsanfrage des Clients
*/
private static String
LOGIN_ACK
= "LA";
/**
* Spielanfrage an einen verfuegbaren Spieler auf dem Server
*/
private static String
GAME_REQ
= "RE";
/**
* Ablehnung der Spieleinladung durch den gegnerischen Spieler
*/
private static String
GAME_REJ
= "RJ";
/**
* Annahme der Spieleinladung durch den eingeladen Spieler
*/
private static String
GAME_ACK
= "RA";
/**
* Timout, der dann vom Server an beide Spieler gesendet wird, wenn
* der eingeladene Spieler innerhalb von 20 Sekunden auf die
* Einladungsanfrage nicht antwortet
*/
private static String
TIMEOUT
= "TO";
/**
* Nachricht, die anzeigt, dass der Server beendet wurde, waehrend
* noch Spieler eingewaehlt waren
*/
private static String
SERVER_QUIT
= "SQ";
/**
* Einwahlfenster, dass zum Einwaehlen auf dem Server sowie zum
* Starten eines Netzerkspiels gebraucht wird.
*/
private OpponentChooser chooser;
/**
* Gibt an, ob eine Verbindung zum Server besteht
*/
private boolean
isConnectedToServer;
/**
* Gibt an, ob die Schnittstelle weiterhin Daten empfangen und senden
* koennen soll
*/
private boolean
isStopped;
/**
* Spieler, der den entfernten gegnerischen Spieler lokal simuliert
*/
private PlayerNetwork
playerNetwork;
/**
* Socket, dass zum Empfangen und Senden von Daten gebraucht wird
*/
private SocketConnection playerSocket;
/**
* Stream zum Empfangen von Daten vom Server
Seite 82 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
private DataInputStream receiver;
/**
* Stream zum Senden von Daten an den Server
*/
private DataOutputStream sender;
/**
* Initialisiert den PlayerConnector. Stellt die Verbindung zum
* Server her und startet einen Thread, der Daten auf dem Socket
* empfaengt und sendet
*
* @param connectUrl Adresse des Servers fuer den Verbindungsaufbau
* @param portNum Portnummer der Serveranwendung auf dem Server
*/
public PlayerConnector (String connectUrl, String portNum)
{
try
{
playerSocket
=
(SocketConnection) Connector.open ("socket://localhost:5647");
/*(SocketConnection) Connector.open ("socket://" +
connectUrl + ":" + portNum);
*/
receiver
= playerSocket.openDataInputStream ();
sender
= playerSocket.openDataOutputStream ();
isStopped
= false;
isConnectedToServer = true;
/* Empfange und Sende Daten in einem separaten Thread, damit der
* Client nicht blockiert wird und trotzdem weiterhin das Spiel
* bedienen kann
*/
new Thread (this).start ();
} catch (IOException e)
{
e.printStackTrace ();
};
}
/**
* Sendet die Nachticht GAME_ACK an den einladenden Spieler, um die
* Einladung anzunehmen und eine Spiel zu starten.
*/
public synchronized void acceptGame ()
{
sendMessage (GAME_ACK);
}
/**
* Sendet die Nachricht LOGIN_REQ zusammen mit name an den Server
* um einen Einwahlwunsch kennzuzeichnen.
*
* @param name Benutzername, mit dem sich der Spieler auf dem Server
*
einwaehlen will
*/
public synchronized void login (String name)
{
sendMessage (LOGIN_REQ + " " + name);
}
Seite 83 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
/**
* Sendet die Nachricht LOGOUT_REQ an den Server, um die Verbindung
* zu beenden.
*/
public synchronized void logout ()
{
sendMessage (LOGOUT_REQ);
}
/**
* Sendet die Nachricht GAME_REJ an den einladenden Spieler, wenn
* der Spieler die Einladung abgelehnt hat.
*/
public synchronized void rejectGame ()
{
sendMessage (GAME_REJ);
}
/**
* Sendet die Nachricht GAME_REQ zusammen mit dem Namen des Spielers
* gegen den er spielen moechte zum gegnerischen Spieler.s
*
* @param opponent Name des Spielers, gegen den der Spieler spielen
*
moechte
*/
public synchronized void requestGame (String opponent)
{
sendMessage (GAME_REQ + " " + opponent);
}
/**
* Gibt die Ressourcen fuer den OpponentChooser wieder frei, wenn ein
* Netwerkspiel gestartet wurde.
*/
public void resetChooser ()
{
chooser = null;
}
/**
* Sendet und empfaengt Daten zum und vom Server.
*/
public void run ()
{
String received = null;
while (!isStopped)
{
received = getMessage ();
/* Wenn der Server abstuerzt, dann gibt getMessage null zurueck
* und alle Verbindungen koennen geschlossen werden
*/
if (received == null)
{
closeConnection ();
/* Wenn der Server abstuerzt, waehrend eine zwei Spieler zu
* einem Spiel verbunden werden, wird zum Formular zurueck
* gekehrt in dem die Spielerliste angezeigt wird
*/
Seite 84 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
if (chooser != null)
{
chooser.returnToMenu ();
}
/* Wenn ein Netwerkspiel schon gestartet wurde, wird ins
* Menue zurueck gewechselt, von dem aus das Netwerkspiel
* gestartet wurde
*/
else
{
playerNetwork.resetGame ();
};
}
else
{
handleMessage (received);
};
};
}
/**
* Sendet den gemachten Zug an den entfernten gegnerischen Spieler.
*
* @param positionX x-Koordinate des Zuges
* @param positionY y-Koordinate des Zuges
*/
public synchronized void sendMove (byte positionX, byte positionY)
{
sendMessage (positionX + " " + positionY);
}
/**
* Gibt an, ob eine Verbindung zum Server besteht.
*
* @return true, wenn eine Verbindung zum Server besteht, ansonsten
*
false
*/
public synchronized boolean isConnectedToServer ()
{
return isConnectedToServer;
}
/**
* Setzt den aktuellen OpponentChooser, zu dem zurueck gekehrt wird,
* wenn eine Spieleinladung abgelehnt wird und der Spieler weiterhin
* die Moeglichkeit hat, andere Spieler einzuladen.
*
* @param chooser OpponentChooser, der die Liste der verfuegbaren
*
Spieler anzeigt
*/
public synchronized void setOpponentChooser (OpponentChooser chooser)
{
this.chooser = chooser;
}
/**
* Simuliert den entfernten Spieler durch Setzen der Variable
* playerNetwork.
*
* @param playerNetwork Entfernter Spieler, der lokal simuliert wird
*/
Seite 85 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
public void setPlayer (PlayerNetwork playerNetwork)
{
this.playerNetwork = playerNetwork;
}
/**
* Schliesst die Verbindung zum Server, wenn sich der Client auswaehlt
* oder das Spielt beendet.
*/
private void closeConnection ()
{
try
{
receiver.close ();
sender.close ();
playerSocket.close ();
isStopped = true;
} catch (IOException e)
{
isStopped = true;
e.printStackTrace ();
};
}
/**
* Wertet die empfangenen Nachrichten vom Server aus.
*
* @param message Nachricht, die vom Server empfangen wurde.
*/
private synchronized void handleMessage (String message)
{
/* Eine Befehl des Protokolls zwischen Client und Server besteht
* aus zwei Zeichen am Anfang der Zeichenkette, die versendet wird
*/
String status = message.substring (0, 2);
if (status.equals (PLAYER_QUIT))
{
/* Wenn ein Netwerkspiel schon gestartet wurde, wird geprueft,
* ob die Ressourcen fuer die Formulare zum Einwaehlen schon frei
* gegeben sind
*/
if (chooser == null)
{
sendMessage (LOGOUT_REQ);
}
/* Wenn ein Netwerkspiel noch nicht gestartet wurde und es wird
* PLAYER_QUIT erhalten, so bedeutet das, dass der gegnerische
* Spieler waehrend er eine Einladung zum Spiel erhalten hat,
* sich auf dem Server ausgewaehlt hat
*/
else
{
/* Fuehrt dazu, dass die Formulare zum auswaehlen aus der
* Spielerliste wieder angezeigt werden
*/
chooser.reactOnTimout ();
};
}
else if (status.equals (LOGIN_ACK))
Seite 86 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
{
/* Bedeutet, dass der Server keine Spielernamen mit gesendet hat
* und somit keine verfuegbaren Spieler auf dem Server vorhanden
* sind
*/
if (message.trim ().length () == 2)
{
chooser.evaluateListFromServer ("");
}
else
{
chooser.evaluateListFromServer (message.substring (
message.indexOf (' ') + 1, message.length ()));
};
}
else if (status.equals (LOGIN_REJ))
{
chooser.evaluateListFromServer (null);
}
else if (status.equals (GAME_REQ))
{
chooser.responseToGameRequest (message.substring (
message.indexOf (' ') + 1, message.length ()));
}
else if (status.equals (GAME_ACK))
{
/* Wenn ein Timeout auf einem Socket gesetzt wird und es wird
* wieder zurueck gesetzt, waehrend auf dem Socket noch keine
* Daten empfangen wurden, so hat das Zuruecksetzen keine Wirkung
* bis Daten empfangen wurden. Daher muss, um das Timeout, das
* bei der Einladung eines Spielers gesetzt wurde und in diesem
* Zeitraum der Spieler antworten muss, eine Nachricht an den
* Server geschickt werden, damit das Timout wieder zurueck
* gesetzt werden kann
*/
sendMessage (GAME_ACK);
chooser.startNetworkGame ();
}
else if (status.equals (GAME_REJ))
{
chooser.resetState ();
chooser.showGameRejected ();
sendMessage (GAME_REJ);
}
else if (status.equals (TIMEOUT))
{
chooser.reactOnTimout ();
}
else if (status.equals (LOGOUT_ACK))
{
closeConnection ();
if (chooser != null)
{
chooser.returnToMenu ();
}
else
{
playerNetwork.resetGame ();
};
}
Seite 87 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
else if (status.equals (SERVER_QUIT))
{
closeConnection ();
if (chooser != null)
{
chooser.returnToMenu ();
}
else
{
playerNetwork.resetGame ();
};
}
else
{
/* Falls kein Protokollbefehl fuer den Spielaufbau zwischen
* zwei entfernten Spielern empfangen wird, handelt es sich um
* die Uebermittlung eines Zuges waehrend eines Spiels
*/
playerNetwork.set (Byte.parseByte (message.substring (0, 1)),
Byte.parseByte (message.substring (2, 3)));
};
}
/**
* Sendet eine Nachricht zum Server.
*
* @param message Nachricht, die zum Server gesendet wird
*/
private void sendMessage (String message)
{
try
{
int length = message.length ();
for (byte i = 0; i < length; i++)
{
sender.write ((int) message.charAt (i));
};
sender.write ((int) '\n');
sender.flush ();
} catch (IOException e)
{
e.printStackTrace ();
};
}
/**
* Empfaengt eine Nachricht vom Server.
*
* @return Nachricht, die vom Server empfangen wurde
*/
private String getMessage ()
{
int
c;
StringBuffer buffer = new StringBuffer ();
try
{
Seite 88 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
while ((c = receiver.read ()) != '\n')
{
buffer.append ((char) c);
};
return buffer.toString ();
} catch (IOException e)
{
return null;
};
}
}
Paket renderer
Renderer.java
package renderer;
import graphics3d.Board3D;
import javax.microedition.m3g.Mesh;
/**
* Basisklasse fuer die Zuganimation. Wird von Board3D benutzt, um Zuege
* auf der Anzeige zu animieren.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;Renderer.java<br>
* <b>Date:</b>05.11.2006<br>
* <b>History:</b><br>
*
18.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Graphics und Graphics3D referenz werden nicht mehr direkt
*
ueber geben, sondern sind ueber das Argument board3D
*
zugreifbar<br>
*
13.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Signatur der Funktion renderer geandert, es wird auch
*
coordinatesToDelete ueber geben<br>
*
* @author Mehmet Akin
* @version 1.2
*/
public abstract class Renderer
{
/**
* Ordnet einer x-y-Koordinate jeweils Werte zu, um die das jeweilige
* 3D-Objekt auf dem Spielbrett verschoben werden muss. Die oebere
* rechte Ecke des Spielbretts hat die internen Koordinaten (5,5)
* und auf der grafischen Darstellung muss ein Objekt 25 Einheiten
* in Richtung der x-Achse nach rechts und 25 Einheiten nach oben
* in Richtung der y-Achse verschoben werden, damit es auch oben
* rechts platziert wird. Da die Werte symmetrisch sind reicht eine
* 2-dimensonale Variable zur Zuordnung.
* Beispiel:
* Die internet Koordinate des Steins (3,4) wuerde um BOARD_COORDS[3]
* = 5 Einheiten nach rechts und BOARD_COORDS[4] = 15 Einheiten nach
* oben verschoben werden.
*/
protected static final byte [] BOARD_COORDS = {-25, -15, -5, 5, 15,
25};
/**
* Berechnet die Zuganimation und fuehrt den Zug grafisch auf dem
Seite 89 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* Spielbrett aus.
*
* @param x x-Koordinate des Zugs
* @param y y-Koordinate des Zugs
* @param sheep Stein als Schaf, das gesetzt werden soll
* @param indicesToDelete Indices, an denen die gegnerischen Steine
*
gedreht werden muessen, weil sie geschlagen wurden
* @param coordinatesDeleted Koordinaten in internen Brett, die
*
gedreht wurden
* @param board3D Grafische Darstellung des Spielbretts, auf dem die
*
Zuganimation durchgefuehrt werden soll
*/
public abstract void render (byte x, byte y, Mesh sheep,
byte [] indicesToDelete,
byte [][] coordinatesDeleted,
Board3D board3D);
}
RendererBombSet.java
package renderer;
import graphics3d.Board3D;
import java.io.IOException;
import javax.microedition.lcdui.Image;
import javax.microedition.m3g.Appearance;
import javax.microedition.m3g.CompositingMode;
import javax.microedition.m3g.Group;
import javax.microedition.m3g.Image2D;
import javax.microedition.m3g.Mesh;
import javax.microedition.m3g.Sprite3D;
import javax.microedition.m3g.Transform;
/**
* Fuehrt eine Zuganimation grafisch durch. Dazu wird ein Bombenzeunder
* an die Stelle verschoben, an der gesetzt werden soll. Anschliessend
* fliegt ein Schaf von dem oberen Teil der Anzeige auf den
* Bombenzuender, dass dazu fuehrt, dass an den Stellen, an denen Schafe
* des Gegners gedreht werden sollen, Rauch in Form von Sprite3D* Objekten auftritt und so eine Explosion der Schafe simuliert.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;RendererBombSet.java<br>
* <b>Date:</b>05.11.2006<br>
* <b>History:</b><br>
*
19.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Funktion render implementiert<br>
*
* @author Mehmet Akin
* @version 1.1
*/
public class RendererBombSet extends Renderer
{
/**
* Pfad zum Bild von halbtransparentem Rauch, dass die Explosion eines
* Schafes simuliert
*/
private static String smokeImagePath = "/res/images/smoke.png";
/**
* Bombenzuender, der an die Stelle verschoben wird, an dem der
* Spieler setzen moechte
*/
private Mesh
dynamite;
Seite 90 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
/**
* Stellt das Rauch-Objekt im 3D-Raum dar
*/
private Sprite3D
smoke;
/**
* Erzeugt ein Objekt, dass den Rauch darstellt.
*/
public RendererBombSet ()
{
createSmoke ();
};
/**
* @see Renderer#render(byte, byte, Mesh, byte[], byte[][], Board3D)
*/
public void render (byte x, byte y, Mesh sheep,
byte [] indicesToDelete,
byte [][] coordinatesDeleted, Board3D board3D)
{
Group
smokes
= new Group ();
Group
sheeps
= new Group ();
Transform sheepTransform
= new Transform ();
Transform smokeTransform
= new Transform ();
Transform dynamiteTransform = new Transform ();
int
deletedLength
= coordinatesDeleted.length;
Mesh
sheepToSet
= (Mesh) sheep.duplicate ();
dynamite = (Mesh) board3D.getBomb ().duplicate ();
sheepTransform.setIdentity ();
dynamiteTransform.setIdentity ();
sheepTransform.postTranslate (BOARD_COORDS [x], BOARD_COORDS [y],
0);
sheepTransform.postTranslate (0, 0, 100);
dynamiteTransform.postTranslate (BOARD_COORDS [x], BOARD_COORDS [y],
0);
dynamiteTransform.postTranslate (-100, 0, 0);
sheeps.addChild (dynamite);
/* Fuehrt dazu, dass der Bombenzuender von links nach rechts auf
* das zu setzende Feld animiert wird
*/
for (byte i = 0; i < 10; i++)
{
dynamiteTransform.postTranslate (10, 0, 0);
dynamite.setTransform (dynamiteTransform);
board3D.renderNode (sheeps, null);
};
sheeps.addChild (sheepToSet);
/* Nachdem der Bombenzuender sich auf dem zu setzenden Feld befindet
* kann das zu setzende Schaf von oben nach unten auf den
* Bombenzuender fliegen und die Explosion der Schafe initiieren
*/
for (byte i = 0; i < 10; i++)
{
sheepTransform.postTranslate (0, 0, -9.5f);
sheepToSet.setTransform (sheepTransform);
board3D.renderNode (sheeps, null);
Seite 91 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
};
board3D.getBoard ().addChild (sheeps);
board3D.createBackground ();
for (byte i = 0; i < deletedLength; i++)
{
byte
directionX = coordinatesDeleted [i][0];
byte
directionY = coordinatesDeleted [i][1];
Sprite3D smokeCopy = (Sprite3D) smoke.duplicate ();
smokeTransform.setIdentity ();
smokeTransform.postTranslate (BOARD_COORDS [directionX] + 1,
BOARD_COORDS [directionY] + 1, 0);
smokeTransform.postScale (12, 12, 1);
smokeCopy.setTransform (smokeTransform);
smokes.addChild (smokeCopy);
board3D.getController ().getSoundPlayer ().playSoundBomb ();
board3D.renderNode (smokes, null);
/* Dauer, in der der Rauch auf dem Feld, auf dem das gegnerische
* Schaf geschlagen wurde angezeigt wird
*/
try
{
Thread.sleep (200);
} catch (InterruptedException e)
{
e.printStackTrace ();
};
}
board3D.getBoard ().removeChild (sheeps);
int
Transform
Mesh
Mesh
indicesLength = indicesToDelete.length;
transform
= new Transform ();
sheepToSetCopy;
sheepToDeleteCopy;
for (int i = 0; i < indicesLength; i++)
{
sheepToSetCopy
= (Mesh) sheep.duplicate ();
sheepToDeleteCopy = board3D.getSheep (indicesToDelete [i]);
sheepToDeleteCopy.getTransform (transform);
sheepToSetCopy.setTransform (transform);
board3D.getBoard ().removeChild (sheepToDeleteCopy);
board3D.addSheepAtIndex (sheepToSetCopy, indicesToDelete [i]);
transform.setIdentity ();
};
sheepToSetCopy = (Mesh) sheep.duplicate ();
transform.postTranslate (BOARD_COORDS [x], BOARD_COORDS [y], 0);
sheepToSetCopy.setTransform (transform);
board3D.addSheep (sheepToSetCopy);
for (int i = indicesToDelete.length - 1; i >= 0; i--)
{
board3D.getBoard ().removeChild (
Seite 92 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
board3D.getSheep (indicesToDelete [i]));
};
}
/**
* Liefert das Rauch-Objekt, das zum Simulieren der Explosion eines
* Schafes benutzt wird.
*
* @return Rauch-Objekt zum Simulieren der Explosion eines Schafes
*/
public Sprite3D getSmoke ()
{
return smoke;
}
/**
* Erzeugt das Rauchobjekt als
*/
private void createSmoke ()
{
Appearance
app
=
CompositingMode compMode
=
Image2D
smokeImage =
2D-Bild im 3-dimensionalen Raum.
new Appearance ();
new CompositingMode ();
null;
try
{
smokeImage = new Image2D (Image2D.RGBA,
Image.createImage (smokeImagePath));
} catch (IOException e)
{
e.printStackTrace ();
};
compMode.setBlending (CompositingMode.ALPHA);
app.setCompositingMode (compMode);
smoke = new Sprite3D (true, smokeImage, app);
}
}
RendererFlySet.java
package renderer;
import graphics3d.Board3D;
import javax.microedition.m3g.Group;
import javax.microedition.m3g.Mesh;
import javax.microedition.m3g.Transform;
/**
* Fuehrt die Zuganimation durch, in dem fuer jedes Schaf, das in die
* gegnerische Farbe gedreht werden soll, ein Schaf der anderen Farbe
* vom oberen Teil der Anzeige nach unten auf das Schaf fliegt. Auf
* das zu setzende Feld fliegt auch ein Schaf.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;RendererFlySet.java<br>
* <b>Date:</b>05.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
Seite 93 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
public class RendererFlySet extends Renderer
{
/**
* @see Renderer#render(byte, byte, Mesh, byte[], byte[][], Board3D)
*/
public void render (byte x, byte y, Mesh sheep,
byte [] indicesToDelete,
byte [][] coordinatesDeleted, Board3D board3D)
{
Mesh
sheepToSet;
Group
sheeps
= new Group ();
Transform transform = new Transform ();
for (int i = indicesToDelete.length - 1; i >= 0; i--)
{
transform.setIdentity ();
Mesh tempSheep = board3D.getSheep (indicesToDelete [i]);
tempSheep.getTransform (transform);
sheepToSet = (Mesh) sheep.duplicate ();
sheepToSet.setTransform (transform);
sheeps.addChild (sheepToSet);
board3D.getBoard ().removeChild (tempSheep);
board3D.addSheepAtIndex (sheepToSet, indicesToDelete [i]);
};
sheepToSet = (Mesh) sheep.duplicate ();
transform.setIdentity ();
transform.postTranslate (BOARD_COORDS [x], BOARD_COORDS [y], 0);
sheepToSet.setTransform (transform);
sheeps.addChild (sheepToSet);
transform.setIdentity ();
transform.postTranslate (0, 0, 100);
/* Die Schafe fliegen nun auf das zu setzende Feld, sowie auf alle
* Felder, auf denen Schafe der gegnerischen Farbe gedreht werden
* muessen
*/
for (byte i = 0; i < 10; i++)
{
transform.postTranslate (0, 0, -10);
sheeps.setTransform (transform);
board3D.renderNode (sheeps, transform);
};
board3D.addSheep (sheepToSet);
for (int i = indicesToDelete.length - 1; i >= 0; i--)
{
sheeps.removeChild (board3D.getSheep (indicesToDelete [i]));
};
sheeps.removeChild (sheepToSet);
}
}
Seite 94 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
RendererNormalSet.java
package renderer;
import graphics3d.Board3D;
import javax.microedition.m3g.Mesh;
import javax.microedition.m3g.Transform;
/**
* Fuehrt eine Zuganimation durch indem einfach im Hintergrund alle
* Schafe, die gedreht werden muessen durch ein Schaf der anderen Farbe
* im Szenegraphen ersetzt werden und auf dem zu setzenden Feld auch
* ein Schaf der zu setzenden Farbe hinzu gefuegt wird. Nach der neuen
* Zusammenstellung des Szenegraphen wird dieser auf der Anzeige
* gerendert.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;RendererNormalSet.java<br>
* <b>Date:</b>05.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
public class RendererNormalSet extends Renderer
{
/**
* @see Renderer#render(byte, byte, Mesh, byte[], byte[][], Board3D)
*/
public void render (byte x, byte y, Mesh sheep,
byte [] indicesToDelete,
byte [][] coordinatesDeleted, Board3D board3D)
{
int
indicesLength = indicesToDelete.length;
Transform transform
= new Transform ();
Mesh
sheepToSet;
Mesh
sheepToDelete;
for (int i = 0; i < indicesLength; i++)
{
sheepToSet
= (Mesh) sheep.duplicate ();
sheepToDelete = board3D.getSheep (indicesToDelete [i]);
sheepToDelete.getTransform (transform);
sheepToSet.setTransform (transform);
board3D.getBoard ().removeChild (sheepToDelete);
board3D.addSheepAtIndex (sheepToSet, indicesToDelete [i]);
transform.setIdentity ();
};
sheepToSet = (Mesh) sheep.duplicate ();
transform.postTranslate (BOARD_COORDS [x], BOARD_COORDS [y], 0);
sheepToSet.setTransform (transform);
board3D.addSheep (sheepToSet);
}
}
Paket sound
SoundPlayer.java
Seite 95 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
package sound;
import java.io.IOException;
import java.io.InputStream;
import javax.microedition.media.Manager;
import javax.microedition.media.MediaException;
import javax.microedition.media.Player;
/**
* Spielt Audiodateien ab, die waehrend dem Spiel zu bestimmten
* Situationen gebraucht werden.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;SoundPlayer.java<br>
* <b>Date:</b>28.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
public class SoundPlayer
{
/**
* Audiospieler zum Abspielen der Audiodatei, die eine Explosion
* simuliert
*/
private Player playerBomb;
/**
* Audiospieler zum Abspielen der Audiodatei beim Waehlen eines
* ungueltigen Spielfeldes durch den Spieler
*/
private Player playerErrorSet;
/**
* Audiospieler zum Abspielen der Audiodatei, wenn ein Spieler
* erfolgreich einen Zug durch gefuehrt hat
*/
private Player playerSet;
/**
* Audiospieler zum Abspielen der Audiodatei, wenn das Plane zum
* Auswaehlen eines Spielfeldes bewegt wird
*/
private Player playerSlide;
/**
* Initialisiert alle Audiospieler, die benoetigt werden.
*/
public SoundPlayer ()
{
InputStream in = null;
try
{
in
= getClass ().getResourceAsStream (
"/res/sound/sndSlidePlane.mp3");
playerSlide = Manager.createPlayer (in, "audio/mpeg");
/* Fuehrt dazu, dass die Audiodateien nicht erst vor dem ersten
* Abspielen in den Speicher geladen wird, sondern schon beim
* Initialisieren, was Verzoegerungen beim Abspielen aussschliesst
*/
playerSlide.realize ();
playerSlide.prefetch ();
in
= getClass ().getResourceAsStream (
Seite 96 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
"/res/sound/sndErrorSet.mp3");
playerErrorSet = Manager.createPlayer (in, "audio/mpeg");
playerErrorSet.realize ();
playerErrorSet.prefetch ();
in
= getClass ().getResourceAsStream (
"/res/sound/sndSheep.mp3");
playerSet = Manager.createPlayer (in, "audio/mpeg");
playerSet.realize ();
playerSet.prefetch ();
in
= getClass ().getResourceAsStream (
"/res/sound/sndBomb.mp3");
playerBomb = Manager.createPlayer (in, "audio/mpeg");
playerBomb.realize ();
playerBomb.prefetch ();
} catch (MediaException e)
{
e.printStackTrace ();
} catch (IOException e)
{
e.printStackTrace ();
};
}
/**
* Spielt den Sound einer Explosion ab.
*/
public synchronized void playSoundBomb ()
{
playSound (playerBomb);
}
/**
* Spielt einen Audiodatei ab, wenn der Spieler gewonnen oder verloren
* hat.
*
* @param isWinner true, wenn Audiodatei fuer einen Sieg abgespielt
*
werden soll, false, wenn Audiodatei fuer eine Niederlage
*
abgespielt werden soll
*/
public synchronized void playSoundEndGame (boolean isWinner)
{
if (isWinner)
{
playSound ("/res/sound/sndWinner.mp3");
}
else
{
playSound ("/res/sound/sndLoser.mp3");
};
}
/**
* Spielt Audiodatei ab, der Spieler ein ungueltiges Spielfeld zum
* Setzen gewaehlt hat.
*/
public synchronized void playSoundErrorSet ()
Seite 97 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
{
playSound (playerErrorSet);
}
/**
* Spielt Audiodatei ab, wenn der Spieler erfolgreich einen Stein
* gesetzt hat.
*/
public synchronized void playSoundSet ()
{
playSound (playerSet);
}
/**
* Spielt Audiodatei ab, wenn das Plane zum Kennzeichnen des aktuell
* ausgewaehlten Spielfeldes verschoben wird.
*/
public synchronized void playSoundSlide ()
{
playSound (playerSlide);
}
/**
* Spielt eine Audiodatei bei Spielanfang ab.
*/
public synchronized void playSoundStartGame ()
{
playSound ("/res/sound/sndStart.mp3");
}
/**
* Spielt eine Audiodatei ab.
*
* @param player Audiospieler, der aktiviert werden und die
*
Audiodatei abspielen soll
*/
private void playSound (Player player)
{
try
{
player.stop ();
player.realize ();
player.prefetch ();
player.start ();
} catch (MediaException e)
{
e.printStackTrace ();
};
}
/**
* Spielt eine bestimmte Audiodatei ab, die uebergeben wird.
*
* @param soundPath Pfad zu der Audiodatei, die abgespielt werden soll
*/
private void playSound (String soundPath)
{
InputStream in
= null;
Player
player = null;
try
Seite 98 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
{
in
= getClass ().getResourceAsStream (soundPath);
player = Manager.createPlayer (in, "audio/mpeg");
player.realize ();
player.prefetch ();
player.start ();
} catch (MediaException e)
{
e.printStackTrace ();
} catch (IOException e)
{
e.printStackTrace ();
};
}
}
Paket graphics3d
Board3D.java
package graphics3d;
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
main.GameController;
renderer.RendererFlySet;
renderer.RendererNormalSet;
renderer.Renderer;
renderer.RendererBombSet;
reversi.Board;
java.io.IOException;
java.util.Random;
java.util.Vector;
javax.microedition.lcdui.Graphics;
javax.microedition.lcdui.Image;
javax.microedition.m3g.Appearance;
javax.microedition.m3g.Background;
javax.microedition.m3g.Camera;
javax.microedition.m3g.CompositingMode;
javax.microedition.m3g.Graphics3D;
javax.microedition.m3g.Group;
javax.microedition.m3g.Image2D;
javax.microedition.m3g.Light;
javax.microedition.m3g.Mesh;
javax.microedition.m3g.Node;
javax.microedition.m3g.Sprite3D;
javax.microedition.m3g.Transform;
javax.microedition.m3g.World;
/**
* Stellt die interne Darstellung des Bretts grafisch im 3-dimensinalen
* Raum dar. Die 3D-Landschaft besteht dabei aus einer Wiese, auf dem
* ein Gitter platziert ist, das das 6x6 Spielfeld dar stellt. Um das
* Spielfeld herum sind Baueme platziert. Die Spielsteine werden durch
* schwarze und weisse Schafe dargestellt. Fuer die 3D-Programmierung
* wird das Mobile Java 3D API nach JSR-184 Spezifikation benutzt.
* <br><br>
*
* <b>File:</b>&nbsp;&nbsp;Board3D.java<br>
* <b>Date:</b>03.11.2006<br>
* <b>History:</b><br>
Seite 99 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*
29.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Weisser und schwarzer Schafskopf wurden dem Spielfeld in
*
der oberen linken und rechten Ecke hinzugefügt,
*
Pfeilbild wird angezeigt, um anzuzeigen welche Spieler
*
an der Reihe ist<br>
*
28.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Beim Wechseln des gewaehlten Spielfeldes sowie beim Setzen
*
eines Steins oder beim auswaehlen eines ungueltigen
*
Feldes und bei Spielanfang/-ende werden Audiodateien
*
abgespielt<br>
*
26.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Spielnachrichten werden bei Anfang und Ende des Spiels
*
angezeigt<br>
*
24.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Baeume in Form von 3D-Sprites wurden um das Spielfeld
*
platziert<br>
*
21.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Gras besteht nun aus einer 8x8 gekachelten Textur,
*
Spielbrett wird nicht mehr modelliert, sondern wird
*
als ein 6x6 Gitter aus Quadstrips dargestellt<br>
*
13.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Steine, die gedreht werden muessen, werden nun an die
*
Funktion set uebergeben<br>
*
04.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Brett wird mit 3D-Schafen besetzt<br>
*
* @author Mehmet Akin
* @version 1.7
*/
public class Board3D
{
/**
* Gibt die Entfernung an, mit der ein Punkt von der Kamera mindestens
* entfernt sein muss, um auf der Anzeige projiziert zu werden
*/
private static final float NEAR_CLIPPING_PLANE = 0.1f;
/**
* Gibt den Betrachtungswinkel der Kamera in Richtung der y-Achse an
*/
private static final float FOVY
= 60;
/**
* Gibt die Entfernung an, mit der ein Punkt von der Kamera maximal
* entfernt sein darf, um auf der Anzeige projiziert zu werden
*/
private static final float FAR_CLIPPING_PLANE = 1000f;
/**
* Gibt das Bildseitenverhaeltnis an
*/
private static float
aspectRatio;
/**
* Hintergrundbild, mit dem bei immediate mode rendering der
* Hintergrund geloescht wird
*/
private Background
backGround;
/**
* Bild, dass beim immediate mode rendering gebraucht wird, um den
* Hintergrund zu loeschen
*/
private Image2D
backgroundImage;
/**
* Stellt ein schwarzes Schaf im 3-dimensionalen Raum dar
Seite 100 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
private Mesh
blackSheep;
/**
* Stellt das Spielfeld dar. Alle Schafe, die sich auf dem Spielfeld
* befinden sind Kinder des Spilfeldes und werden bei einer
* Transformation des boards auch transformiert
*/
private Group
board;
/**
* Stellt den Bombenzuender dar. Wird von RendererBombSet gebraucht
* um ein Schaf bei einer Zuganimation explodieren zu lassen
*/
private Mesh
bomb;
/**
* Kamera, die beim Rendering benutzt wird
*/
private Camera
camera;
/**
* Referenz auf den gameController
*/
private GameController
controller;
/**
* x-Koordinate des Feldes, das der Spieler aktuell gewaehlt hat
*/
private byte
currentPosX;
/**
* y-Koordinate des Feldes, das der Spieler aktuell gewaehlt hat
*/
private byte
currentPosY;
/**
* Referenz auf die Anzeige, um zu zeichnen
*/
private Graphics
g;
/**
* Dient fuer das Rendering des Szenegraphen sowie einzelner Objekte
* des Szenegraphen
*/
private Graphics3D
g3d;
/**
* Bild eines Pfeils, das nach links zeigt. Wird gebraucht um auf der
* Anzeige kenntlich zu machen, dass der weisse Spieler an der Reihe
* ist, einen Zug zu machen
*/
private Image
imgArrowLeft;
/**
* Bild eines Pfeils, das nach rechts zeigt. Wird gebraucht um auf der
* Anzeige kenntlich zu machen, dass der schwarze Spieler an der Reihe
* ist, einen Zug zu machen
*/
private Image
imgArrowRight;
/**
* Bild eines schwarzen Schafskopfes. Wird rechts oben auf der Anzeige
* dargestellt. Wenn der schwarze Spieler an der Reihe ist, zeigt der
* gruene Pfeil in Richtung dieses Kopfes
*/
private Image
imgBlackSheep;
/**
* Bild eines weissen Schafskopfes. Wird links oben auf der Anzeige
* dargestellt. Wenn der weisse Spieler an der Reihe ist, zeigt der
* gruene Pfeil in Richtung dieses Kopfes
*/
Seite 101 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
private Image
imgWhiteSheep;
/**
* Gibt an, ob der schwarze Spieler an der Reihe ist
*/
private boolean
isBlackSheepsTurn;
/**
* Zufallszahlengenerator wird gebraucht, um einen Renderer aus den
* moeglichen zu waehlen, damit dieser die entsprechende Zuganimation
* durch fuehren kann
*/
private Random
numberGenerator;
/**
* Viereck (in Blender Plane genannt), dass auf der x- und y-Achse
* bewegt werden kann, um das aktuell ausgewaehlte Feld kennzuzeichnen
*/
private Mesh
plane;
/**
* Transformationsmatrix zum Verschieben des plane auf der x-y-Achse
*/
private Transform
planeTransform;
/**
* 3D-Schafe werden in einer Vector-Datenstruktur gespeichert. Damit
* aus der internen Datenstruktur des Spielbretts mit x- und y* Koordinaten ein Zugriff auf das entsprechende 3D-Objekt moeglich
* ist wird zu jedem Stein, der intern durch eine x- und y- Koordinate
* bestimmt ist, der Vektorindex, unter dem sich das 3D-Schaf befindet
* gespeichert
*/
private byte [][]
posToSheepMapper;
/**
* Uebernehmen die Zuganimation, wenn ein Spieler einen Stein setzt
*/
private Renderer []
renderer;
/**
* Vektor, um die Schafe in Form von3D-Objekten zu speichern
*/
private Vector
sheeps;
/**
* Weisses Schaf als 3D-Objekt
*/
private Mesh
whiteSheep;
/**
* Szenegraph des Spiels. Besteht aus einem Untergrund, der ein Mesh
* mit einer Wiesentextur ist, einem Gitter aus TriangleStrips fuer
* das Spielfeld, Schafen, die als Mesh benutzt werden und Bauemen,
* in Form von Sprite3D-Objekten dar gestellt werden
*/
private World
worldScene;
/**
* Initialisiert alle fuer das 3D-Brett notwendigen Variablen. Setzt
* die Kamera des Szenegraphen inklusive Werte fuer die
* Perspektivprojektion
*
* @param controller Referenz auf den GameController
*/
public Board3D (GameController controller)
{
Object3DLoader board3DLoader = new Object3DLoader ();
/* Vorgegebener Modus ist Direktional Licht
*/
Light
light
= new Light ();
Seite 102 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
this.controller
g3d
g
numberGenerator
backGround
posToSheepMapper
=
=
=
=
=
=
controller;
Graphics3D.getInstance ();
controller.getGraphicsInstance ();
new Random ();
new Background ();
new byte [Board.MAX_X][Board.MAX_Y];
for (byte i = 0; i < Board.MAX_X; i++)
{
for (byte j = 0; j < Board.MAX_Y; j++)
{
posToSheepMapper [i][j] = -1;
};
};
currentPosX
currentPosY
worldScene
aspectRatio
=
=
=
=
renderer
renderer [0]
renderer [1]
renderer [2]
sheeps
planeTransform
camera
=
=
=
=
=
=
=
4;
3;
board3DLoader.getWorldScene ();
(float) controller.getWidth () /
controller.getHeight ();
new Renderer [3];
new RendererFlySet ();
new RendererBombSet ();
new RendererNormalSet ();
new Vector (4, 1);
new Transform ();
worldScene.getActiveCamera ();
/* Benutze Perspectivprojektion fuer das Rendering der Objekt
camera.setPerspective (FOVY, aspectRatio, NEAR_CLIPPING_PLANE,
FAR_CLIPPING_PLANE);
g3d.setCamera (camera, null);
worldScene.setActiveCamera (camera);
worldScene.addChild (light);
plane
whiteSheep
blackSheep
bomb
=
=
=
=
*/
board3DLoader.getPlane ();
board3DLoader.getWhiteSheep ();
board3DLoader.getBlackSheep ();
board3DLoader.getBomb ();
initBoard ();
buildGround ();
backgroundImage = new Image2D (Image2D.RGB, controller.getWidth (),
controller.getHeight ());
light.setIntensity (1.25f);
try
{
imgWhiteSheep = Image.createImage
imgBlackSheep = Image.createImage
imgArrowLeft = Image.createImage
imgArrowRight = Image.createImage
} catch (IOException e)
{
e.printStackTrace ();
};
("/res/images/whiteSheep.png");
("/res/images/blackSheep.png");
("/res/images/arrowLeft.png");
("/res/images/arrowRight.png");
/* Erzeuge das Hintergrundbild, das am Anfang im Immediate Mode
Seite 103 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* gebraucht, um den Hintergrund zu loeschen
*/
createBackground ();
renderPlane ();
/* Zeichne die beiden Schafskoepfe am oberen Teil der Anzeige,
* mit dem gruenen Pfeil in Richtung des weissen Schafskopfes, weil
* dieser den ersten Zug macht
*/
drawSheepsTurn ();
/* Wenn alle benoetigten 3D-Objekte geladen wurden, wird der Lader
* nicht mehr gebraucht und die Ressourcen koennen wieder frei
* gegegeben werden
*/
board3DLoader = null;
}
/**
* Sobald ein Schaf gesetzt wurde, wird addSheep gebraucht, um das
* Schaf zu der Liste der schon vorhandenen hinzu zu fuegen
*
* @param sheep Schaf als Mesh, dass zur Szene hinzu gefuegt wird
*/
public void addSheep (Mesh sheep)
{
sheeps.addElement (sheep);
posToSheepMapper [currentPosX][currentPosY] =
(byte) (sheeps.size () - 1);
}
/**
* Fuegt ein Schaf unter einem bestimmten Index zu den schon
* vorhandenen Schafen hinzu
*
* @param sheep Schaf als Mesh, dass hinzugefuegt werden soll
* @param index Index, unter dem das Schaf eingefuegt werden soll
*/
public void addSheepAtIndex (Mesh sheep, byte index)
{
sheeps.setElementAt (sheep, index);
}
/**
* Loescht den Hintergrund der Anzeige. Dafuer muss zuerst das
* Graphics-Objekt an das Graphics3D-Objekt gebunden werden. Danach
* kann der Hintergrund mit dem Bild geloescht werden. Danach wird
*/
public void clearBackground ()
{
g3d.bindTarget (g);
g3d.clear (backGround);
g3d.releaseTarget ();
flushGraphics ();
}
/**
* Fuehrt dazu, dass der gruene Pfeil, der im oberen Teil des Menues
* angezeigt wird, nun in die andere Richtung zeigt, um deutlich zu
* machen, dass nun der andere Spieler an der Reihe ist
*/
Seite 104 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
public void changePlayer ()
{
isBlackSheepsTurn = !isBlackSheepsTurn;
renderPlane ();
}
/**
* Erstellt den Hintergrund, mit dem die Anzeige im immediate mode
* rendering geloescht wird
*/
public void createBackground ()
{
/* Nachdem ein Zug animiert wurde, wird nun aus dem aktuellen
* Szenegraphen ein Hintergrundbild erzeugt, dass nun die neue
* Ausgangsbasis zum Loeschen des Hintergrundes im Immediate Mode
* ist
*/
g3d.bindTarget (backgroundImage);
board.removeChild (plane);
g3d.render (worldScene);
board.addChild (plane);
g3d.releaseTarget ();
backGround.setImage (backgroundImage);
}
/**
* Bewegt das Plane ein Feld in Richtung der negativen y-Achse(runter)
*/
public void moveDown ()
{
controller.getSoundPlayer ().playSoundSlide ();
--currentPosY;
planeTransform.postTranslate (0, -10f, 0);
renderPlane ();
}
/**
* Bewegt das Plane ein Feld in Richtung der negativen x-Achse(links)
*/
public void moveLeft ()
{
controller.getSoundPlayer ().playSoundSlide ();
--currentPosX;
planeTransform.postTranslate (-10, 0, 0);
renderPlane ();
}
/**
* Bewegt das Plane ein Feld in Richtung der positiven x-Achse(rechts)
*/
public void moveRight ()
{
controller.getSoundPlayer ().playSoundSlide ();
++currentPosX;
Seite 105 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
planeTransform.postTranslate (10, 0, 0);
renderPlane ();
}
/**
* Bewegt das Plane ein Feld in Richtung der positiven y-Achse(hoch)
*/
public void moveUp ()
{
controller.getSoundPlayer ().playSoundSlide ();
++currentPosY;
planeTransform.postTranslate (0, 10f, 0);
renderPlane ();
}
/**
* Rendert den kompletten Szenegraphen im Retained Mode.
*/
public void render ()
{
g3d.bindTarget (g);
g3d.render (worldScene);
g3d.releaseTarget ();
flushGraphics ();
}
/**
* Rendert entweder ein einziges Objekt oder eine Gruppe von Objekten
* im Immediate Mode.
*
* @param node Einzelner oder Gruppe von Knoten, die gerendert wird
* @param transform Transformationsmatrix, mit der das Node Objekt
*
vor dem Rendering manipuliert wird
*/
public void renderNode (Node node, Transform transform)
{
g3d.bindTarget (g);
g3d.clear (backGround);
g3d.render (node, transform);
g3d.releaseTarget ();
drawSheepsTurn ();
flushGraphics ();
}
/**
* Zeigt die Nachricht message auf dem Bildschirm an. Wird benutzt,
* um Nachrichten bei Spielanfang und -ende anzuzeigen
*
* @param message Nachricht, die angezeigt werden soll
*/
public void showEndMessage (Sprite3D message)
{
showMessage (message, 0);
}
/**
* Zeigt die Nachricht message bei Spielstart auf der Anzeige
*
* @param message Nachricht, die angezeigt wird
Seite 106 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
public void showStartMessage (Sprite3D message)
{
showMessage (message, 1000);
}
/**
* Liefert das Spielbrett, auf dem sich die Spielsteine befinden
*
* @return Spielbrett, auf dem gesetzt wird
*/
public Group getBoard ()
{
return board;
}
/**
* Liefert das 3D-Objekt fuer den Bombenzuender
*
* @return Bombenzuender, das zum explodieren des Schafes benoetigt
*
wird
*/
public Mesh getBomb ()
{
return bomb;
}
/**
* Liefert die Referenz auf den GameController
*
* @return GameController, der den Spielfluss steuert
*/
public GameController getController ()
{
return controller;
}
/**
* Liefert ein Schaf an einer bestimmten Stelle im Vektor, in dem
* alle Schafe gespeichert sind, die sich aktuell auf dem Spielfeld
* befinden
*
* @param index Index, an dem das Schaf sich befindet
* @return Schaf als 3D-Objelt an dem Index index
*/
public Mesh getSheep (byte index)
{
return (Mesh) sheeps.elementAt (index);
}
/**
* Fuehrt einen im internen Spielbrett ausgefuehrten Zug grafisch auf
* der Anzeige durch. Dazu waehlt er zufaellig einen Renderer, der
* die Animation des Zuges berechnet.
*
* @param x x-Koordinate des Steins, das gesetzt werden soll
* @param y y-Koordinate des Steins, das gesetzt werden soll
* @param colorToSet Farbe des Spielers, der gesetzt hat
* @param indicesToDelete Menge der Indices, an denen die Schafe im
*
Vektor geloescht werden muessen, weil diese durch den
*
Spieler geschlagen wurden
Seite 107 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* @param coordinatesDeleted x-y-Koordinaten aller Schafe, die
*
geschlagen wurden
*/
public void set (byte x, byte y, byte colorToSet,
byte [] indicesToDelete,
byte [][] coordinatesDeleted)
{
int sheepCount = sheeps.size () + 1;
Mesh sheep
= (colorToSet == Board.WHITE_PLAYER)
? (Mesh) whiteSheep.duplicate ()
: (Mesh) blackSheep.duplicate ();
byte tempCurX
= currentPosX;
byte tempCurY
= currentPosY;
currentPosX = x;
currentPosY = y;
/* Waehle aus allen Renderern, die zur Verfuegung stehen, einen
* Zug zu animieren einen zufaellig aus
*/
renderer [numberGenerator.nextInt (3)].render (x, y, sheep,
indicesToDelete, coordinatesDeleted, this);
/* Der Renderer fuehrt nur die Zuganimation durch und fuegt die
* gesetzten Schafe und gedrehten Schafe nicht dem Szenegraph hinzu.
* Daher wird das hier selber gemacht
*/
for (byte i = 0; i < sheepCount; i++)
{
Mesh tempSheep = (Mesh) sheeps.elementAt (i);
if (tempSheep.getParent () == null)
{
board.addChild (tempSheep);
};
};
currentPosX = tempCurX;
currentPosY = tempCurY;
createBackground ();
renderPlane ();
}
/**
* Zeigt das im Hintergrund gerenderte Bild im Vordergrund.
*/
private void flushGraphics ()
{
controller.flushGraphics ();
}
/**
* Initialisiert das Spielbrett bei Spielanfang. Dazu setzt es ein
* weisses Schaf an die Position (2|2) und eins an (3|3) und jeweils
* ein schwarzes Schaf an die Position (2|3) und (3|2).
*/
private void initBoard ()
{
Mesh
whiteSheep1
= (Mesh) whiteSheep.duplicate ();
Mesh
whiteSheep2
= (Mesh) whiteSheep.duplicate ();
Seite 108 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Mesh
Mesh
Transform
Transform
Transform
Transform
blackSheep1
blackSheep2
trWhiteSheep1
trWhiteSheep2
trBlackSheep1
trBlackSheep2
=
=
=
=
=
=
(Mesh) blackSheep.duplicate ();
(Mesh) blackSheep.duplicate ();
new Transform ();
new Transform ();
new Transform ();
new Transform ();
board = new BoardGrid3D ().getBoard3DGrid ();
/* Verschiebe das Plane zum Auswaehlen des Spielfeldes auf die
* Koordinate (4,3)
*/
planeTransform.postTranslate (15, 4.8f, 0);
plane.setTransform (planeTransform);
sheeps.addElement (whiteSheep1);
/* Um von 2D-internen Koordinaten eines Steines auf dem Brett auf
* das 3D-Objekt zuzugreifen muessen die der Liste hinzugefuegt
* werden
*/
posToSheepMapper [2][2] = 0;
sheeps.addElement (whiteSheep2);
posToSheepMapper [3][3] = 1;
sheeps.addElement (blackSheep1);
posToSheepMapper [2][3] = 2;
sheeps.addElement (blackSheep2);
posToSheepMapper [3][2] = 3;
trWhiteSheep1.postTranslate (-5, -5, 0);
trWhiteSheep2.postTranslate (5, 5, 0);
trBlackSheep1.postTranslate (-5, 5, 0);
trBlackSheep2.postTranslate (5, -5, 0);
whiteSheep1.setTransform (trWhiteSheep1);
whiteSheep2.setTransform (trWhiteSheep2);
blackSheep1.setTransform (trBlackSheep1);
blackSheep2.setTransform (trBlackSheep2);
board.addChild (whiteSheep1);
board.addChild (whiteSheep2);
board.addChild (blackSheep1);
board.addChild (blackSheep2);
board.addChild (plane);
}
/**
* Rendert das Plane, das zum Anzeigen des aktuell vom Spieler
* gewaehlten Spielfeldes benutzt wird.
*/
private void renderPlane ()
{
byte index = posToSheepMapper [currentPosX][currentPosY];
g3d.bindTarget (g);
g3d.clear (backGround);
g3d.render (plane, planeTransform);
Seite 109 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
/* Damit das Plane nicht ueber den Schafen gerendert wird, muss
* geprueft werden, ob sich ein Schaf auf der Position befindet,
* auf den das Plane verschoben wurde. Dann kann das Schaf ueber
* das Plane gerendert werden und es entsteht der Eindruck das das
* Plane unter dem Schaf bewegt wird
*/
if (index != -1)
{
Transform t
= new Transform ();
Mesh
sheep = (Mesh) sheeps.elementAt (index);
Group
group = new Group ();
sheep.getTransform (t);
group.addChild ((Mesh) sheep.duplicate ());
g3d.render (group, null);
};
g3d.releaseTarget ();
drawSheepsTurn ();
flushGraphics ();
}
/**
* Fuegt ein Bild eines Baums auf die Wiese. Das Bild ist ein
* Sprite3D-Objekt, dass das Bild wie ein 3D-Modell wirken laesst,
* indem man das Bild in alle 3-Achsenrichtungen verschieben kann.
*
* @param ground Wiese, auf dem der Baum platziert werden soll
* @param treePath Pfad zur Bilddatei
* @param transX Wert, um den der Baum auf der x-Achse verschoben wird
* @param transY Wert, um den der Baum auf der y-Achse verschoben wird
* @param transZ Wert, um den der Baum auf der z-Achse verschoben wird
* @param scaleX Wert, um den der Baum auf der x-Achse skaliert wird
* @param scaleY Wert, um den der Baum auf der y-Achse skaliert wird
* @param scaleZ Wert, um den der Baum auf der z-Achse skaliert wird
*/
private void addTree (Group ground, String treePath, int transX,
int transY, int transZ, int scaleX, int scaleY,
int scaleZ)
{
Transform transform = new Transform ();
Sprite3D tree
= createTree (treePath);
transform.postTranslate (transX, transY, transZ);
transform.postScale (scaleX, scaleY, scaleZ);
tree.setTransform (transform);
ground.addChild (tree);
}
/**
* Bildet den Untergrund der Spielelandschaft, die eine Wiese bildet.
* Darauf werden Baueme platziert.
*/
private void buildGround ()
{
Group
ground
= new Group ();
Mesh
grass
= new Ground3D ().getGrass ();
Transform transform = new Transform ();
transform.postTranslate (0, 0, -5f);
transform.postRotate (90, 1, 0, 0);
Seite 110 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
transform.postScale (12, 1, 12);
grass.setTransform (transform);
ground.addChild (grass);
ground.addChild (board);
addTree (ground, "/res/images/tree4.png",
addTree (ground, "/res/images/tree1.png",
addTree (ground, "/res/images/tree3.png",
addTree (ground, "/res/images/tree3.png",
addTree (ground, "/res/images/tree2.png",
addTree (ground, "/res/images/tree5.png",
addTree (ground, "/res/images/tree5.png",
addTree (ground, "/res/images/tree3.png",
addTree (ground, "/res/images/tree1.png",
addTree (ground, "/res/images/tree2.png",
addTree (ground, "/res/images/tree4.png",
addTree (ground, "/res/images/tree3.png",
addTree (ground, "/res/images/tree1.png",
addTree (ground, "/res/images/tree4.png",
addTree (ground, "/res/images/tree3.png",
worldScene.addChild (ground);
-20,
5,
-12,
20,
28,
30,
-35,
-37,
-35,
-35,
-15,
-2,
20,
29,
27,
31,
32,
26,
27,
15,
-4,
30,
10,
-5,
-30,
-35,
-37,
-33,
-16,
-33,
11,
12,
11,
11,
14,
14,
10,
11,
12,
14,
11,
11,
12,
13,
13,
15,
20,
15,
15,
22,
22,
22,
15,
20,
22,
15,
15,
20,
15,
15,
15,
20,
15,
15,
22,
22,
22,
15,
20,
22,
15,
15,
20,
15,
15,
1);
1);
1);
1);
1);
1);
1);
1);
1);
1);
1);
1);
1);
1);
1);
}
/**
* Erzeugt ein Baum-Objekt, das auf der Anzeige gerendert werden kann.
*
* @param treePath Pfad zur Bilddatei des Baumes
* @return Baum, der im 3D-Raum bewegt werden kann
*/
private Sprite3D createTree (String treePath)
{
Image2D
treeImage = null;
Sprite3D
tree
= null;
Appearance
app
= new Appearance ();
CompositingMode compMode = new CompositingMode ();
try
{
treeImage = new Image2D (Image2D.RGBA,
Image.createImage (treePath));
} catch (IOException e)
{
e.printStackTrace ();
};
/* Damit der transparent Hintergrund der Baumbilder auch wirklich
* beim Rendering transparent ist, muss der Alpha-Wert der
* Bildpunkte beruecksichtigt werden
*/
compMode.setBlending (CompositingMode.ALPHA);
app.setCompositingMode (compMode);
tree = new Sprite3D (true, treeImage, app);
return tree;
}
/**
* Zeichnet einen weissen Schafskopf oben links in der Anzeige und
* einen schwarzen Schafskopf oben rechts sowie einen gruenen Pfeil
* in Richtung des schwarzen oder weissen Kopfes, je nachdem welcher
* Spieler gerade am Zug ist.
Seite 111 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
private void drawSheepsTurn ()
{
g.drawImage (imgWhiteSheep, 0, 0, Graphics.TOP | Graphics.LEFT);
g.drawImage (imgBlackSheep, controller.getWidth (), 0,
Graphics.TOP | Graphics.RIGHT);
if (isBlackSheepsTurn)
{
g.drawImage (imgArrowRight, controller.getWidth () - 65, 4,
Graphics.TOP | Graphics.LEFT);
}
else
{
g.drawImage (imgArrowLeft, 26, 4, Graphics.TOP | Graphics.LEFT);
};
}
/**
* Zeigt eine 3D-Nachricht auf dem Bildschirm an. Wenn time 0 ist,
* dann bleibt die Nachricht auf der Anzeige, ansonsten erlischt sie
* nach der Zeit time
*
* @param message Nachricht, die angezeigt werden soll
* @param time Dauer, in der die Nachricht angezeigt wird
*/
private void showMessage (Sprite3D message, int time)
{
Transform transform = new Transform ();
transform.postScale (30, 25, 10);
renderNode (message, transform);
if (time != 0)
{
try
{
Thread.sleep (time);
renderPlane ();
} catch (InterruptedException e)
{
e.printStackTrace ();
};
};
}
}
BoardGrid3D.java
package graphics3d;
import
import
import
import
import
import
import
import
javax.microedition.m3g.Appearance;
javax.microedition.m3g.Group;
javax.microedition.m3g.IndexBuffer;
javax.microedition.m3g.Mesh;
javax.microedition.m3g.Transform;
javax.microedition.m3g.TriangleStripArray;
javax.microedition.m3g.VertexArray;
javax.microedition.m3g.VertexBuffer;
/**
Seite 112 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* Stellt ein schwarzes Gitter dar, das das 6x6 Spielfeld repraesentiert
* Das Gitter besteht aus 7 horizontalen und 7 vertikalen Linien, die
* ueberkreuzt 36 Felder ergeben.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;BoardGrid3D.java<br>
* <b>Date:</b>21.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
class BoardGrid3D
{
/**
* Liefert das Spielfeld, das aus 7 horizontalen und 7 vertikalen
* Linien besteht
*
* @return Spielfeld, auf dem gesetzt wird
*/
public Group getBoard3DGrid ()
{
return buildGrid ();
}
/**
* Stellt das Gitter, das das Spieldfeld repraesentiert zusammen.
* Dafuer erzeugt es 7 vertikale und 7 horizontale Linien im Ursprung
* und verschiebt diese so in Richtung der x- und y-Achse, dass ein
* 6x6 Spielfeldgitter ensteht.
*
* @return Spielfeld als Gitter aus Linien
*/
private Group buildGrid ()
{
Mesh
rowLine
= createLine ();
Mesh
columnLine
= (Mesh) rowLine.duplicate ();
Transform baseTransformX = new Transform ();
Transform baseTransformY = new Transform ();
Transform transform
= new Transform ();
Group
grid
= new Group ();
baseTransformX.postScale (30, 0.4f, 0);
baseTransformY.postScale (0.4f, 30, 0);
int j = 0;
/* Erzeuge 7 Linien, die in Richtung der y-Achse skaliert werden
* und somit vertikale Linien des Gitters bilden
*/
for (byte i = -3; i <= 3; i++)
{
transform.setIdentity ();
transform.postTranslate (0, i * 10, 0);
transform.postMultiply (baseTransformX);
rowLine = (Mesh) rowLine.duplicate ();
rowLine.setTransform (transform);
grid.addChild (rowLine);
j++;
};
/* Erzeuge 7 Linien, die in Richtung der x-Achse skaliert werden
* und somit horizontale Linien des Gitters bilden
*/
Seite 113 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
for (byte i = -3; i <= 3; i++)
{
transform.setIdentity ();
transform.postTranslate (i * 10, 0, 0);
transform.postMultiply (baseTransformY);
columnLine = (Mesh) columnLine.duplicate ();
columnLine.setTransform (transform);
grid.addChild (columnLine);
};
return grid;
}
/**
* Erzeugt eine schwarze Linie am Ursprung mit der Laenge eins.
*
* @return Linie am Ursprung, das zur Zusammenstellung des Spielfeldes
*
gebraucht wird.
*/
private Mesh createLine ()
{
IndexBuffer triangles;
VertexBuffer vertexBuffer = new VertexBuffer ();
/* Die Eckpunkte des Vierecks
*/
short vertices [] = new short [] {-1, -1, 0,
1, -1, 0,
1, 1, 0,
-1, 1, 0};
/* Die Linie soll die Farbe Schwarz haben
*/
short vertexColors [] = new short [] {0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0};
/* Definiert, dass ein Punkt aus 3 aufeinanderfolgenden Zahlen
* im byte-Array besteht
*/
VertexArray vertexArray = new VertexArray (vertices.length / 3, 3,
2);
/* Definiert, dass eine Farbe fuer einen Eckpunkt aus 3
* aufeinanderfolgenden Zahlen im byte-Array besteht
*/
VertexArray colorArray
= new VertexArray (vertexColors.length /
3, 3, 1);
/* Gibt die Reihenfolge an, in der die Eckpunkte zu einem Viereck
* verbunden werden sollen
*/
int
indices []
= new int [] {0, 1, 3, 2};
/* Vier aufeinander folgende Zahlen im indices-Array bilden eine
* TriangleStripArray, dass ein Viereck bildet
*/
int [] stripLengths = new int [] {4};
/* Wir brauchen eine Appearance Objekt, dem wir eine Textur zuweisen
* koennen
*/
Appearance
appearance
= new Appearance ();
vertexArray.set (0, vertices.length / 3, vertices);
vertexBuffer.setPositions (vertexArray, 1.0f, null);
vertexBuffer.setColors (colorArray);
triangles = new TriangleStripArray (indices, stripLengths);
Seite 114 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
return new Mesh (vertexBuffer, triangles, appearance);
}
}
Ground3D.java
package graphics3d;
import java.io.IOException;
import javax.microedition.lcdui.*;
import javax.microedition.m3g.*;
/**
* Erstellt den Untergrund fuer die Spielelandschaft. Der Untergrund
* ist ein Mesh-Objekt, dass in 64 Kacheln unterteilt ist, von denen
* jeder einzeln eine Textur zugewiesen bekommt,um die Oberflaeche
* detailliert aussehen zu lassen.
* Dieser Code ist eine Version des in dem Buch "Killer Game Programming
* " gezeigten Beispiels ueber das <A href="killerGameProgramming.pdf">
* gekachelte Texturzuweisen</A>, adaptiert an dieses Spiel<br><br>
*
* <b>File:</b>&nbsp;&nbsp;Ground3D.java<br>
* <b>Date:</b>20.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
class Ground3D
{
/**
* Anzahl der Kacheln pro Zeile und Spalte
*/
private static final byte numberTilesPerRow = 8;
/**
* Kachelanzahl des gesamten Wiesenuntergrundes
*/
private static final byte numberTiles
= numberTilesPerRow *
numberTilesPerRow;
/**
* Untergrund, dem die Wiesentextur zugewiesen wird
*/
private Mesh
floorMesh;
/**
* Initialiesiert den Untergrund, der eine Wiese in der Landschaft
* darstellt.
*/
public Ground3D ()
{
/* Ein Mesh wird aus den Punkten, die das Objekt im 3D-Raum
* beschreiben, aus der Reihenfolge wie diese Punkte verbunden
* werden sollen und einer Appearance, die die aeussere Erscheinung
* des Objektes in Form einer Farbe oder Textur wieder gibt
*/
floorMesh = new Mesh (createVertexBuffer (),
createTriangleStrips (), createAppearance ());
}
/**
* Liefert das Objekt, das den Untergrund mit der Wiesentextur
Seite 115 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* darstellt.
*
* @return Untergrund mit einer Wiesentextur
*/
public Mesh getGrass ()
{
return floorMesh;
}
/**
* Erstellt eine Textur und weisst sie einem Appearance-Objekt zu.
* Diese wird einem Mesh-Objekt zugewiesen, der den eigentlichen
* Untergrund der Landschaft darstellt.
*
* @return Textur-Objekt, das eine Wiese darstellt
*/
private Appearance createAppearance ()
{
Appearance appearance = new Appearance ();
PolygonMode polygonMode = new PolygonMode ();
Texture2D
texture
= null;
try
{
texture = new Texture2D (new Image2D (Image2D.RGBA,
Image.createImage ("/res/images/grass.png")));
} catch (IOException e)
{
e.printStackTrace ();
};
appearance.setTexture (0, texture);
/* Damit die Textur nicht verzerrt auf dem Bildschirm dargestellt
* wird, muss die Perspektiv-Korrektur eingeschaltet werden
*/
polygonMode.setPerspectiveCorrectionEnable (true);
appearance.setPolygonMode (polygonMode);
return appearance;
}
/**
* Erstellt ein Feld, dass aus 64 Kacheln besteht.
*
* @return Feld, das aus 64 Kacheln besteht
*/
private TriangleStripArray createTriangleStrips ()
{
int
pos1
= 1;
int
pos2
= 2;
int
pos3
= 0;
int
pos4
= 3;
int [] indices
= new int [4 * numberTiles];
int [] stripLengths = new int [numberTiles];
/*
*
*
*
*
*
An der linken oberen Ecke (-4,4) wird angefangen, den
Wiesenuntergrund zusammenzustellen. Eine Kachel ist ein Quadrat
Die linke untere Ecke hat den Index 0, die rechte untere Ecke 1,
die obere rechte Ecke den Index 2, die obere linke Ecke den Index
3. Die erste Kachel wird zusammen gestellt in dem die vier Punkte
in der Reihenfolge (1,2,0,3) zu einem TriangleStripArray
Seite 116 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* verbunden werden. Fuer alle anderen
* Reihenfolge fuer die Verbindung der
* daher sehr leicht fuer diese analog
*/
for (int i = 0; i < 4 * numberTiles; i
{
indices [i]
= pos1;
pos1
+= 4;
indices [i + 1] = pos2;
pos2
+= 4;
indices [i + 2] = pos3;
pos3
+= 4;
indices [i + 3] = pos4;
pos4
+= 4;
};
Kacheln wird die gleiche
Eckpunkte genommen und kann
berechnet werden
+= 4)
for (int i = 0; i < numberTiles; i++)
{
/* Legt fest, dass immer vier aufeinander folgende Punkte zu einem
* TriangleStrip zusammengefasst werden soll. Das heisst, dass der
* vierte Punkte mit dem zweiten verbunden wird und so ein Viereck
* bilden kann
*/
stripLengths [i] = 4;
};
return new TriangleStripArray (indices, stripLengths);
}
/**
* Berechnet alle Punkte des Untegrundes, das eine Wiese darstellen
* soll, die aus 64 Kacheln besteht.
*
* @return Koordinaten aller Punkte, aus denen der Untergrund der
*
Spielelandschaft bestehen soll
*/
private VertexBuffer createVertexBuffer ()
{
/* Da jede Kachel aus vier Eckpunkten bestehen, die durch eine 3D* dargestellt werden, werden 3*4 = 12 mal 64 Zahlen gebraucht,
* um jede Kachel des Untergrundes darzustellen
*/
short []
vertices
= new short [12 * numberTiles];
/* Weil jedem der vier Eckpunkte einer Kachel eine 2D-Koordinate
* fuer die Zuweisung einer Textur zugeordnet werden muss,
* werden 2*4 = 8 mal 64 Zahlen gebraucht, um jeder Kachel einzeln
* eine Textur zuzuweisen
*/
short []
textureCoords = new short [8 * numberTiles];
VertexArray vertexArray
= null;
VertexArray textureArray = null;
VertexBuffer vertexBuffer = new VertexBuffer ();
int
i
= 0;
/* Fange in der linken oberen Ecke des Untergrundes, in diesem Fall
* bei den Koordinaten (-4,4) an und erzeuge die einzelnen 3D* Koordinaten fuer jeden der 64 Kacheln
*
*/
for (int j = (-numberTilesPerRow / 2) + 1;
j <= numberTilesPerRow / 2; j++)
Seite 117 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
{
for (int k = -numberTilesPerRow / 2;
k <= (numberTilesPerRow / 2) - 1; k++)
{
vertices [i]
= (short) k;
vertices [i + 1] = 0;
vertices [i + 2] = (short) j;
vertices [i + 3] = (short) (k + 1);
vertices [i + 4] = 0;
vertices [i + 5] = (short) j;
vertices [i + 6] = (short) (k + 1);
vertices [i + 7] = 0;
vertices [i + 8] = (short) (j - 1);
vertices [i + 9] = (short) k;
vertices [i + 10] = 0;
vertices [i + 11] = (short) (j - 1);
i
+= 12;
};
};
vertexArray = new VertexArray (vertices.length / 3, 3, 2);
vertexArray.set (0, vertices.length / 3, vertices);
/* Dem oberen linken Eckpunkt eines Quadrates wird die
* Texturkoordinate (0,0), dem unterem linken Punkt (0,1), dem
* rechtem unteren Punkt (1,1) und dem rechten oberen Punkt (1,0).
* Die Wiesentextur wird angefangen in der linken unteren Ecke
* in der Reihenfolge {0,1, 1,1, 1,0, 0,0} jeder einzelnen Kachel
* zugewiesen.
*/
for (int j = 0; j < 8 * numberTiles; j += 8)
{
textureCoords [j]
= 0;
textureCoords [j + 1] = 1;
textureCoords [j + 2] = 1;
textureCoords [j + 3] = 1;
textureCoords [j + 4] = 1;
textureCoords [j + 5] = 0;
textureCoords [j + 6] = 0;
textureCoords [j + 7] = 0;
};
textureArray = new VertexArray (textureCoords.length / 2, 2, 2);
textureArray.set (0, textureCoords.length / 2, textureCoords);
vertexBuffer.setPositions (vertexArray, 1.0f, null);
vertexBuffer.setTexCoords (0, textureArray, 1.0f, null);
return vertexBuffer;
}
}
MessageCreator.java
package graphics3d;
import
import
import
import
import
import
java.io.IOException;
javax.microedition.lcdui.Image;
javax.microedition.m3g.Appearance;
javax.microedition.m3g.CompositingMode;
javax.microedition.m3g.Image2D;
javax.microedition.m3g.Sprite3D;
Seite 118 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
/**
* Erzeugt die 3D-Nachrichten, die bei Spielanfang oder -ende angezeigt
* werden sollen.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;MessageCreator.java<br>
* <b>Date:</b>26.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
public class MessageCreator
{
/**
* Pfad zum Bild fuer die Nachricht "White starts", die bei
* Spielanfang angezeigt wird, wenn zwei menschliche Spieler
* gegeneinander spielen
*/
private static String pathWhiteBegins
=
"/res/messages/whiteStarts.png";
/**
* Pfad zum Bild fuer die Nachricht "You start", die beim einladenden
* Spieler in einem Netzwerkspiel oder bei einem Spiel gegen das Handy
* bei Spielanfang angezeigt wird
*/
private static String pathYouBegin
=
"/res/messages/youStart.png";
/**
* Pfad zum Bild fuer die Nachricht "Opponent starts", die beim
* eingeladenen Spieler in einem Netzwerkspiel bei Spielanfang
* angezeigt wird
*/
private static String pathRemotePlayerBegins =
"/res/messages/opponentStarts.png";
/**
* Pfad zum Bild fuer die Nachricht "You win", die bei Spielende
* angezeigt wird, wenn ein Spieler gegen das Handy oder in einem
* Netzwerkspiel gewinnt
*/
private static String pathYouWin
=
"/res/messages/youWin.png";
/**
* Pfad zum Bild fuer die Nachricht "You lose", die bei Spielende
* angezeigt wird, wenn ein Spieler gegen das Handy oder in einem
* Netzwerkspiel verliert
*/
private static String pathYouLose
=
"/res/messages/youLose.png";
/**
* Pfad zum Bild fuer die Nachricht "White wins", wenn in einem
* Spiel zwischen zwei menschlichen Spielern der weisse Spieler
* gewinnt
*/
private static String pathWhiteWins
=
"/res/messages/whiteWins.png";
/**
* Pfad zum Bild fuer die Nachricht "Tie", die in jedem Spieltyp
* angezeigt wird, sobald das Spiel mit einem Unentschieden endet
*/
private static String pathTie
=
Seite 119 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
"/res/messages/tie.png";
/**
* Pfad zum Bild fuer die Nachricht "Black wins", wenn in einem
* Spiel zwischen zwei menschlichen Spielern der weisse Spieler
* gewinnt
*/
private static String pathBlackWins
=
"/res/messages/blackWins.png";
/**
* Liefert die Nachricht, die angezeigt wird, wenn der schwarze
* Spieler gewinnt.
*
* @return Nachricht, die die Zeichenkette "Black wins" darstellt
*/
public static Sprite3D getMessageBlackWins ()
{
return createMessage (pathBlackWins);
}
/**
* Liefert die Nachricht, die beim eingeladenen Spieler angezeigt
* wird.
*
* @return Nachricht, die die Zeichenkette "Opponent begins" darstellt
*/
public static Sprite3D getMessageRemotePlayerBegins ()
{
return createMessage (pathRemotePlayerBegins);
}
/**
* Liefert die Nachricht, die angezeigt wird, wenn das Spiel in einem
* Unentschieden endet.
*
* @return Nachricht, die die Zeichenkette "Tie" darstellt
*/
public static Sprite3D getMessageTie ()
{
return createMessage (pathTie);
}
/**
* Liefert die Nachricht, die angezeigt wird, wenn der weisse Spieler
* das Spiel startet.
*
* @return Nachricht, die die Zeichenkette "White starts" darstellt
*/
public static Sprite3D getMessageWhiteBegins ()
{
return createMessage (pathWhiteBegins);
}
/**
* Liefert die Nachricht, die angezeigt wird, wenn der schwarze
* Spieler gewinnt.
*
* @return Nachricht, die die Zeichenkette "White wins" darstellt
*/
public static Sprite3D getMessageWhiteWins ()
Seite 120 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
{
return createMessage (pathWhiteWins);
}
/**
* Liefert die Nachricht, die bei dem Spieler bei Spielanfang
* angezeigt wird, die ein Netzwerkspiel initiiert hat oder wenn ein
* Spieler gegen das Handy Spielt.
*
* @return Nachricht, die die Zeichenkette "You start" darstellt
*/
public static Sprite3D getMessageYouBegin ()
{
return createMessage (pathYouBegin);
}
/**
* Liefert die Nachricht, die angezeigt wird, wenn der schwarze
* Spieler gewinnt.
*
* @return Nachricht, die die Zeichenkette "White wins" darstellt
*/
public static Sprite3D getMessageYouLose ()
{
return createMessage (pathYouLose);
}
/**
* Liefer die Nachricht, die angezeigt wird, wenn ein Spieler in einem
* Netzwerkspiel oder gegen das Handy gewinnt.
*
* @return Nachricht, die die Zeichenkette "You win" darstellt
*/
public static Sprite3D getMessageYouWin ()
{
return createMessage (pathYouWin);
}
/**
* Erzeugt ein 2D-Bild, dass im dreidimensionalen Raum dargestellt
* werden kann.
*
* @param path Pfad zu der Bilddatei, die dargestellt werden soll
* @return Objekt, dass das Bild im dreidimesionalen Raum darstellt
*/
private static Sprite3D createMessage (String path)
{
Image2D
treeImage = null;
Sprite3D
message
= null;
Appearance
app
= new Appearance ();
CompositingMode compMode = new CompositingMode ();
try
{
treeImage = new Image2D (Image2D.RGBA, Image.createImage (path));
} catch (IOException e)
{
e.printStackTrace ();
};
/* Damit der transparente Hintergrund der Bilder auch transparent
Seite 121 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* angezeigt wird, muessen die Alpha-Werte der Bildpunkte
* beruecksichtigt werden
*/
compMode.setBlending (CompositingMode.ALPHA);
app.setCompositingMode (compMode);
message = new Sprite3D (true, treeImage, app);
return message;
}
}
Object3DLoader.java
package graphics3d;
import
import
import
import
import
javax.microedition.m3g.Group;
javax.microedition.m3g.Loader;
javax.microedition.m3g.Mesh;
javax.microedition.m3g.Object3D;
javax.microedition.m3g.World;
/**
* Liest die 3D-Modelle, die mit Blender3D modelliert wurden und in
* eine M3G-Datei exportiert wurden ein. Die Modelle beinhalten ein
* weisses und ein schwarzes Schafsmodell als Spielsteine, eine Plane
* die auf dem Spielfeld in Richtung x- und y-Achse bewegt werden kann,
* um zu erkennen welches Spielfeld der Spieler aktuell ausgewaehlt hat
* und ein Modell eines Bombenzuenders, das gebraucht wird, um die
* Explosion eine Schafes zu simulieren.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;Object3DLoader.java<br>
* <b>Date:</b>03.11.2006<br>
* <b>History:</b><br>
*
22.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
3D-Modell fuer das Brett wird nicht mehr eingelesen, weil
*
es im Board3D programmatisch zusammengebaut wird<br>
*
* @author Mehmet Akin
* @version 1.1
*/
class Object3DLoader
{
/**
* Stellt das Modell eines schwarzen Schafes als Spielstein dar
*/
private Mesh blackSheep;
/**
* Stellt das Spielbrett dar, auf dem die Spielsteine platziert werden
*/
private Group board;
/**
* Stellt den Bombenzuender zur Explosionssimulation eines Schafes dar
*/
private Mesh bomb;
/**
* Stellt eine Plane(Viereck) dar, die auf der x-y Ebene bewegt wird,
* um kenntlich zu machen, welches Spielfeld der Spieler aktuell
* ausgewaehlt hat
*/
private Mesh plane;
/**
Seite 122 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* Stellt das
*/
private Mesh
/**
* Stellt den
*/
private World
Modell eines weissen Schafes als Spielstein dar
whiteSheep;
Szenegrapen der 3D-Umgebung dar
worldScene;
/**
* Initialisiert den Szenegraphen
*/
public Object3DLoader ()
{
try
{
Object3D [] buffer = Loader.load ("/res/board/sheep.m3g");
worldScene = (World) buffer [0];
buffer
= null;
} catch (Exception e)
{
e.printStackTrace ();
};
whiteSheep = (Mesh) worldScene.find (29);
/* Dieser Aufrug muss aufgrund eines Fehlers in der Mobile Java
* 3D-API gemacht werden, damit zum ersten mal ein Kind des Graphen
* entfernt werden kann
*/
whiteSheep.getParent ().find (whiteSheep.getUserID ());
worldScene.removeChild (whiteSheep);
blackSheep = (Mesh) worldScene.find (36);
worldScene.removeChild (blackSheep);
plane
= (Mesh) worldScene.find (13);
worldScene.removeChild (plane);
bomb
= (Mesh) worldScene.find (48);
worldScene.removeChild (bomb);
}
/**
* Liefer den Spielstein fuer den schwarzen Spieler
*
* @return Schwarzes Schafsmodell als Spielstein
*/
public Mesh getBlackSheep ()
{
return blackSheep;
}
/**
* Liefert das Spielfeld, auf dem gesetzt wird.
*
* @return Spielfeld, auf dem die Schafe platziert sind
*/
public Group getBoard ()
{
return board;
}
/**
* Liefer das Modell des Bombenzuenders fuer die Explosionssimulation
Seite 123 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* eines Schafes.
*
* @return Modell des Bombenzuenders
*/
public Mesh getBomb ()
{
return bomb;
}
/**
* Liefert die Plane(Viereck), die auf der x-y Ebene bewegt wird,
* um kenntlich zu machen, welches Spielfeld der Spieler aktuell
* ausgewaehlt hat.
*
* @return Planeobjekt, das auf der x-y-Ebene bewegt werden kann
*/
public Mesh getPlane ()
{
return plane;
}
/**
* Liefert den Spielstein fuer den weissen Spieler.
*
* @return Weisses Schafsmodell als Spielstein
*/
public Mesh getWhiteSheep ()
{
return whiteSheep;
}
/**
* Liefert den Szenegraphen, der die Spielelandschaft inklusive
* Schafe als Spielsteine enthaelt.
*
* @return Szenegraph der 3D-Umgebung des Spiels
*/
public World getWorldScene ()
{
return worldScene;
}
}
Paket menu3d
Menu3D.java
package menu3d;
import
import
import
import
import
import
import
import
import
import
import
main.CanvasListener;
main.GameController;
javax.microedition.lcdui.Graphics;
javax.microedition.lcdui.game.GameCanvas;
javax.microedition.m3g.Background;
javax.microedition.m3g.Graphics3D;
javax.microedition.m3g.Image2D;
javax.microedition.m3g.Light;
javax.microedition.m3g.Mesh;
javax.microedition.m3g.Transform;
javax.microedition.m3g.World;
Seite 124 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
/**
* Basisklasse fuer das Startmenue beim Starten des Programms sowie
* alle Untermenues, die aus dem Startmenue zu erreichen sind.
* Bietet den Unterklassen Funktionen an, die es dem Spieler
* ermoeglichen, Spieloptionen des entsprechenden Menues auszuwaehlen.
* <br><br>
*
* <b>File:</b>&nbsp;&nbsp;Menu3D.java<br>
* <b>Date:</b>21.10.2006<br>
* <b>History:</b><br>
*
24.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Funktion renderMenu und createBackground hinzu gefuegt<br>
*
* @author Mehmet Akin
* @version 1.1
*/
public abstract class Menu3D implements CanvasListener, Runnable
{
/**
* Spielsteuerung, die Tastenbetaetigungen des Spielers an diese
* Klasse weiter leitet
*/
protected GameController controller;
/**
* Gibt die aktuelle in dem entsprechenden Menue ausgewaehlte Option
* an
*/
protected int
currentStatus;
/**
* Wird zum Laden der Optionen in Form von 3D-Textobjekten gebraucht
*/
protected Menu3DLoader
menu3dLoader;
/**
* Zugriff auf das Zeichenfeld, auf dem grafische Elemente dargestellt
* werden
*/
protected Graphics
g;
/**
* Wird fuer das Rendering der 3D-Modelle benutzt
*/
protected Graphics3D
g3d;
/**
* Variable, die beim Rotieren der Optionen inkrementiert wird. Sobald
* i einen Wert von 36 erreicht hat wird es wieder auf 0 gesetzt,
* sie nicht kontiuierlich steigt
*/
protected int
i;
/**
* Wird beim Rotieren der 3D-Optionen gebraucht. Solange die Variable
* auf true gesetzt ist, ist das entsprechende Menue dasjenige, das
* aktuell Zugriff auf die Anzeige hat
*/
protected boolean
isMenuActive;
/**
* Szenegraph des Menues, dass aus einem Hintergrund sowie den
* einzelnen Optionen des Menues besteht
*/
protected World
menu;
/**
* Optionen, die der Spieler im enstsprechenden Menue auswaehlen kann
*/
Seite 125 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
protected Mesh []
options;
/**
* Hintergrundbild, mit dem der Hintgrung der Anzeige geloescht wird
*/
private
Image2D
backgroundImage;
/**
* Transformationsmatrix, die benutzt wird um die Option zu rotieren
*/
private
Transform
transform;
/**
* Hintergrund-Objekt, dem das Hintergrundbild zugewiesen wird, mit
* dem der Hintergrund der Anzeige geloescht wird
*/
private
Background
backGround;
/**
* Initialisiert das Menue. Erzeugt das Hintergrundbild zum Loeschen
* des Hintergrundes beim Rendering
*
* @param controller Spielsteuerung, die die Tastenbetaetingungen des
*
Spielers an das Menue weiter leitet
*/
public Menu3D (GameController controller)
{
this.controller = controller;
menu3dLoader
= controller.getMenu3DLoader ();
i
= 0;
g3d
= Graphics3D.getInstance ();
g
= controller.getGraphicsInstance ();
backGround
= new Background ();
backgroundImage = new Image2D (Image2D.RGB, controller.getWidth (),
controller.getHeight ());
transform
= new Transform ();
isMenuActive
= true;
}
/**
* Erzeugt das Hintergrundbild.
*/
public void createBackground ()
{
synchronized (g3d)
{
g3d.bindTarget (backgroundImage);
menu.removeChild (options [currentStatus]);
for (int i = options.length - 1; i >= 0; i--)
{
if ((options [i].getParent () == null) && (currentStatus != i))
{
menu.addChild (options [i]);
};
};
g3d.render (menu);
g3d.releaseTarget ();
backGround.setImage (backgroundImage);
};
}
/**
Seite 126 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* Fuehrt dazu, dass die Option, die sich unter der aktuell
* ausgewaehlten Option nun ausgewaehlt wird und rotiert.
*/
public abstract void downKeyPressed ();
/**
* Fuehrt dazu, dass zum Menue gewechselt wird, von aus zu diesem
* Menue gewechselt wurde.
*/
public abstract void exitKeyPressed ();
/**
* Fuehrt dazu, dass die aktuell ausgewaehlte Option ausgewertet wird.
*/
public abstract void fireKeyPressed ();
/**
* Fuehrt dazu, dass die Option, die sich ueber der aktuell
* ausgewaehlten Option nun ausgewaehlt wird und rotiert.
*/
public abstract void upKeyPressed ();
/**
* Ruft je nachdem, welche Taste vom Spieler gedrueckt wurde, die
* entsprechende Funktion auf, die zu einer anderen Option wechselt,
* zu einem anderen Menue zurueck kehrt oder ein Spiel startet.
*/
public void reactOnKeyPressed (int gameAction)
{
if (gameAction == GameCanvas.UP)
{
upKeyPressed ();
}
else if (gameAction == GameCanvas.DOWN)
{
downKeyPressed ();
}
else if (gameAction == GameCanvas.FIRE)
{
fireKeyPressed ();
}
else if (gameAction == GameController.BACK_TO_MENU)
{
exitKeyPressed ();
}
else
{
System.out.println ("Unknown gameAction Error!");
};
}
/**
* Fuehrt dazu, dass das Menue nun das jenige ist, welches Zugriff
* auf die Anzeige hat.
*/
public void reactivate ()
{
isMenuActive = true;
}
/**
Seite 127 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* Rendert die einzelnen Objekte im Immediate Mode. Dazu wird nur
* die ausgewaehlte Option beim staendigen Rotieren gerendert waehrend
* der Rest als Hintgrundbild gezeigt wird.
*/
public void renderMenu ()
{
synchronized (g3d)
{
g3d.bindTarget (g);
g3d.clear (backGround);
g3d.render (options [currentStatus], transform);
g3d.releaseTarget ();
flushGraphics ();
};
}
/**
* Laueft als Thread solange bis zu einem anderen Untermenue
* gewechselt wird.
*/
public void run ()
{
transform.setIdentity ();
while (isMenuActive)
{
try
{
Thread.sleep (50);
} catch (InterruptedException e)
{
e.printStackTrace ();
};
options [currentStatus].getTransform (transform);
/* Wenn eine 360 Grad Rotation erfolgt ist, kann der Wert von i
* auf 0 zurueck gesetzt werden, um ein staendiges wachsen zu
* verhindern
*/
if (i == 36)
{
i = 0;
};
/* Rotiere in 10 Grad Abstaenden
transform.postRotate (10 * i++, 0, 1, 0);
renderMenu ();
};
*/
}
/**
* Startet das Menue als Thread.
*/
public void start ()
{
new Thread (this).start ();
}
/**
* Erzeugt den Szenegraphen. Fuegt ein direktionales Licht und eine
Seite 128 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* Kamera hinzu.
*
* @param menu Szenegraph, dem Licht und eine Kamera hinzu gefuegt
*
wird
*/
protected void createMenu (World menu)
{
this.menu = menu;
this.menu.addChild (new Light ());
/* Im Immediate Mode muss die Kamera explizit gesetzt werden
g3d.setCamera (menu.getActiveCamera (), null);
*/
}
/**
* Zeigt den im Hintergrund gerenderten Bild auf der Anzeige an.
*/
protected void flushGraphics ()
{
controller.flushGraphics ();
}
}
Menu3DLoader.java
package menu3d;
import javax.microedition.lcdui.Image;
import javax.microedition.m3g.Background;
import javax.microedition.m3g.Image2D;
import javax.microedition.m3g.Loader;
import javax.microedition.m3g.Mesh;
import javax.microedition.m3g.Object3D;
import javax.microedition.m3g.World;
/**
* Ladet alle fuer die Menues benoetigten Modelle aus eine M3G-Datei.
* Fuer das Startmenue sowie alle Untermenues, die erreicht werden
* koennen werden separate Dateien benutzt, die alle einem verschiedenen
* World-Objekt zugewiesen werden.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;Menu3DLoader.java<br>
* <b>Date:</b>21.10.2006<br>
* <b>History:</b><br>
*
28.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Hintergrund-Bild wird nun auch geladen<br>
*
24.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
3D-Laden von Modellen implementiert<br>
*
* @author Mehmet Akin
* @version 1.2
*/
public class Menu3DLoader
{
/**
* Menue, in dem die Schwierigkeitsstufe des Handys ausgewaehlt wird
* wenn der Spieler ein Spiel gegen das Handy machen moechte
*/
private World menuGameLevel;
/**
* Menue, das bei Programmbeginn angezeigt wird, aus dem man das Spiel
* beenden kann oder ein Spiel starten kann
Seite 129 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
private World menuGameStart;
/**
* Menue, in dem man auswaehlt, ob man gegen einen weiteren
* menschlichen Spieler, gegen das Handy oder gegen eine entfernten
* Spieler spielen moechte
*/
private World menuGameType;
/**
* Ladet alle Menues.
*/
public Menu3DLoader ()
{
try
{
Object3D [] gameStart
= Loader.load (
"/res/menu/menuGameStart.m3g");
Object3D [] gameLevel = Loader.load (
"/res/menu/menuGameLevel.m3g");
Object3D [] gameType
= Loader.load (
"/res/menu/menuGameType.m3g");
Background background = new Background ();
Image
image
= null;
image = Image.createImage ("/res/images/backGround.png");
background.setImage (new Image2D (Image2D.RGB, image));
menuGameStart = (World) gameStart [0];
menuGameStart.setBackground (background);
menuGameLevel = (World) gameLevel [0];
menuGameLevel.setBackground (background);
menuGameType = (World) gameType [0];
menuGameType.setBackground (background);
/* Gebe nicht mehr benoetigte Ressourcen wieder frei
gameLevel = null;
gameStart = null;
gameType = null;
} catch (Exception e)
{
e.printStackTrace ();
};
*/
}
/**
* Liefert das 3D-Objekt, dass die Option Easy im Menue zum Waehlen
* der Schwierigkeitsstufe gegen das Handy darstellt
*
* @return Option "Easy" fuer das GameLevelMenue
*/
public Mesh getGameLevelOptionEasy ()
{
return (Mesh) menuGameLevel.find (11);
}
/**
* Liefert das 3D-Objekt, dass die Option Hard im Menue zum Waehlen
* der Schwierigkeitsstufe gegen das Handy darstellt
*
Seite 130 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* @return Option "Hard" fuer das GameLevelMenue
*/
public Mesh getGameLevelOptionHard ()
{
return (Mesh) menuGameLevel.find (21);
}
/**
* Liefert das 3D-Objekt, dass die Option Normal im Menue zum Waehlen
* der Schwierigkeitsstufe gegen das Handy darstellt
*
* @return Option "Normal" fuer das GameLevelMenue
*/
public Mesh getGameLevelOptionNormal ()
{
return (Mesh) menuGameLevel.find (16);
}
/**
* Liefert das 3D-Objekt, dass die Option zum Zurueckkehren zum
* Hauptmenue des Handys darstellt.
*
* @return Option "Exit", ueber den man zum Handy-Hauptmenue zurueck
*
kehren kann
*/
public Mesh getGameStartOptionExit ()
{
return (Mesh) menuGameStart.find (16);
}
/**
* Liefert das 3D-Objekt, dass die Option zum Initiieren eines Spiels
* darstellt.
*
* @return Option "Start", ueber den man zum Spieltypauswahlmenue
*
gelangt
*/
public Mesh getGameStartOptionStart ()
{
return (Mesh) menuGameStart.find (11);
}
/**
* Liefert das 3D-Objekt, dass die Option 1P VS 2P darstellt, ueber
* den ein Spieler ein Spiel gegen einen anderen menschlichen Spieler
* starten kann.
*
* @return Option "1P vs 2P" zum Starten eines Spiels zwischen zwei
*
menschlichen Spielern
*/
public Mesh getGameTypeOption1PVs2P ()
{
return (Mesh) menuGameType.find (16);
}
/**
* Liefert das 3D-Objekt, dass die Option 1P vs Com darstellt, ueber
* den ein Spieler ein Spiel gegen das Handy mit der kuenstlichen
* Intelligenz starten kann.
*
* @return Option "1P vs Com" zum Starten eines Spiels zwischen einem
Seite 131 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*
menschlichen Spieler und dem Handy
*/
public Mesh getGameTypeOption1PVsCom ()
{
return (Mesh) menuGameType.find (11);
}
/**
* Liefer das 3D-Objekt, dass die Option 1P vs Net darstellt, ueber
* den ein Spieler ein Spiel einen entfernten Spieler ueber einen
* zentralen GameServer im Internet spielen kann.
*
* @return Option "1P vs Net" zum Starten eines Spiels zwischen zwei
*
menschlichem Spielern ueber einen GameServer im Internet
*/
public Mesh getGameTypeOption1PVsNet ()
{
return (Mesh) menuGameType.find (21);
}
/**
* Liefert das Menu zum Auswaehlen der Schwierigkeitsstufe.
*
* @return Szenegraphen des Menues zum Auswaehlen der
*
Schwierigkeitsstufe
*/
public World getMenuGameLevel ()
{
return menuGameLevel;
}
/**
* Liefert das Startmenue zum Inittieren eines Spiels oder zum
* Zurueck kehren zum Handy-Hauptmenue.
*
* @return Szenegraphen des Startmenues
*/
public World getMenuGameStart ()
{
return menuGameStart;
}
/**
* Liefert das Spieltypauswahlmenue, in dem ausgewaehlt werden kann
* welche Spielart gestartet werden soll
*
* @return Szenegraphen des Spieltypauswahlmenues
*/
public World getMenuGameType ()
{
return menuGameType;
}
}
MenuGameLevel.java
package menu3d;
import ai.IntelligenceEasy;
import ai.IntelligenceHard;
import ai.IntelligenceNormal;
Seite 132 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
import main.GameController;
import player.PlayerAI;
import player.PlayerHuman;
import reversi.Board;
import javax.microedition.m3g.Mesh;
import javax.microedition.m3g.Transform;
/**
* Menue zum Auswaehlen der Schwierigkeitsstufe bei einem Spiel zwischen
* einem menschlichen Spiele gegen das Handy. Der Spieler kann zwischen
* den drei Stufen Easy fuer ein leichtes, Normal fuer eine normale
* und Hard fuer eine sehr schwierige Staerke der kuenstlichen
* Intelligenz, waehlen.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;MenuGameLevel.java<br>
* <b>Date:</b>21.10.2006<br>
* <b>History:</b><br>
*
25.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Kommandozeilenbasiertes auswaehlen der Optionen durch
*
3D-Untermenue ersetzt<br>
*
* @author Mehmet Akin
* @version 1.1
*/
public class MenuGameLevel extends Menu3D
{
/**
* Option fuer eine leichte Schwierigkeitsstufe
*/
private static final int EASY
= 0;
/**
* Option fuer eine normale Schwierigkeitsstufe
*/
private static final int NORMAL = 1;
/**
* Option fuer eine harte Schwierigkeitsstufe
*/
private static final int HARD
= 2;
/**
* Initialisiert das Menue fuer die Auswahl der Schwierigkeitsstufe.
* Option Easy ist die vorgegebene ausgewaehlte Stufe.
*
* @param controller Spielsteuerung, die Tastenbetaetigungen an das
*
Menue weiter leitet
*/
public MenuGameLevel (GameController controller)
{
super (controller);
Transform transform = new Transform ();
createMenu (menu3dLoader.getMenuGameLevel ());
options
options [EASY]
options [NORMAL]
options [HARD]
=
=
=
=
new Mesh [3];
menu3dLoader.getGameLevelOptionEasy ();
menu3dLoader.getGameLevelOptionNormal ();
menu3dLoader.getGameLevelOptionHard ();
/* Verschiebe die Optionen so, wie sie auf der Anzeige dargestellt
* werden sollen
*/
Seite 133 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
transform.postTranslate (0, 23, 0);
options [EASY].setTransform (transform);
transform.setIdentity ();
transform.postTranslate (0, -5, 0);
options [NORMAL].setTransform (transform);
transform.setIdentity ();
transform.postTranslate (0, -33, 0);
options [HARD].setTransform (transform);
createBackground ();
/* Vorselektierte Option beim erstmaligen Anzeigen des Menues
currentStatus = EASY;
*/
}
/**
* Wechselt zu der Option, die sich unter der aktuell augewaehlten
* befindet. Ist die Option Hard aktuell ausgewaehlt, die sich auf der
* Anzeige ganz unten befindet, dann wird zur Option Easy ganz oben
* gewechselt.
*/
public void downKeyPressed ()
{
if (currentStatus == EASY)
{
currentStatus = NORMAL;
}
else if (currentStatus == NORMAL)
{
currentStatus = HARD;
}
else if (currentStatus == HARD)
{
currentStatus = EASY;
}
else
{
System.err.println ("Unknown option error in class Menu" +
"GameLevel!");
};
createBackground ();
}
/**
* Fuehrt dazu, dass das Menue fuer die Schwierigkeitsstufenauswahl
* verlassen und zum Spieltypauswahlmenue, von dem aus es aufgerufen
* wurde zurueck gekehrt wird.
*/
public void exitKeyPressed ()
{
isMenuActive = false;
controller.setDisplayable3D (controller.getMenuGameType ());
}
/**
* Wertet die ausgewaehlte Option aus und startet ein Spiel zwischen
* einem menschlichem Spieler und dem Handy mit der entsprechenden
* Schwierigkeitsstufe.
*/
public void fireKeyPressed ()
{
Seite 134 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
isMenuActive = false;
if (currentStatus == EASY)
{
controller.startGame (new PlayerHuman (Board.WHITE_PLAYER),
new PlayerAI (Board.BLACK_PLAYER,
new IntelligenceEasy ()));
}
else if (currentStatus == NORMAL)
{
controller.startGame (new PlayerHuman (Board.WHITE_PLAYER),
new PlayerAI (Board.BLACK_PLAYER,
new IntelligenceNormal ()));
}
else if (currentStatus == HARD)
{
controller.startGame (new PlayerHuman (Board.WHITE_PLAYER),
new PlayerAI (Board.BLACK_PLAYER,
new IntelligenceHard ()));
}
else
{
System.err.println ("Unknown option error in class Menu" +
"GameLevel!");
};
}
/**
* Wechselt zu der Option, die sich ueber der aktuell augewaehlten
* befindet. Ist die Option Easy aktuell ausgewaehlt, die sich auf der
* Anzeige ganz oben befindet, dann wird zur Option Hard ganz unten
* gewechselt.
*/
public void upKeyPressed ()
{
if (currentStatus == EASY)
{
currentStatus = HARD;
}
else if (currentStatus == NORMAL)
{
currentStatus = EASY;
}
else if (currentStatus == HARD)
{
currentStatus = NORMAL;
}
else
{
System.err.println ("Unknown option error in class Menu" +
"GameLevel!");
};
createBackground ();
}
}
MenuGameStart.java
package menu3d;
Seite 135 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
import main.GameController;
import java.io.IOException;
import javax.microedition.lcdui.Image;
import javax.microedition.m3g.Background;
import javax.microedition.m3g.Image2D;
import javax.microedition.m3g.Mesh;
import javax.microedition.m3g.Transform;
/**
* Startmenue des Spiels. Es besitzt die zwei Optionen Start und Exit.
* Mit Start gelangt man ins Spieltypauswahlmenue, mit Exit kann man
* das Spiel komplett verlassen und zurueck ins Handy-Hauptmenue kehren,
* von aus man das Programm gestartet hat.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;MenuGameStart.java<br>
* <b>Date:</b>21.10.2006<br>
* <b>History:</b><br>
*
25.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Kommandozeilenbasiertes auswaehlen der Optionen durch
*
3D-Untermenue ersetzt<br>
*
* @author Mehmet Akin
* @version 1.1
*/
public class MenuGameStart extends Menu3D
{
/**
* Option, die bei Auswaehlen dazu fuehrt, dass ins
* Spieltypauswahlmenue gewechselt wird
*/
private static final int START_GAME = 0;
/**
* Option, die bei Auswaehlen dazu fuehrt, dass ins
* Hauptmenue des Handys zurueck gekehrt wird
*/
private static final int EXIT_GAME = 1;
/**
* Initialisiert das Startmenue mit den beiden Optionen Start und Exit
*
* @param controller Spielsteuerung, die Tastenbetaetigungen des
*
Spielers an das Menue weiter leitet
*/
public MenuGameStart (GameController controller)
{
super (controller);
Transform transform = new Transform ();
createMenu (menu3dLoader.getMenuGameStart ());
options
= new Mesh [2];
options [START_GAME] = menu3dLoader.getGameStartOptionStart ();
options [EXIT_GAME] = menu3dLoader.getGameStartOptionExit ();
/* Verschiebe die Optionen so, wie sie auf der Anzeige dargestellt
* werden sollen
*/
transform.postTranslate (0, 15, 0);
options [START_GAME].setTransform (transform);
transform.setIdentity ();
transform.postTranslate (0, -15, 0);
Seite 136 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
options [EXIT_GAME].setTransform (transform);
currentStatus = START_GAME;
createBackground ();
}
/**
* Falls die Option Start ausgewaehlt ist, wird Exit ausgewaehlt und
* andersherum.
*/
public void downKeyPressed ()
{
i
= 0;
currentStatus = (currentStatus == START_GAME)
? EXIT_GAME : START_GAME;
createBackground ();
}
/**
* Macht nichts. In den Untermenues fuehrt diese Funktion dazu, dass
* zum Menue gewechselt wird, von dem aus es aufgerufen wurde. Das
* Startmenue ist die Wurzel aller Untermenues.
*/
public void exitKeyPressed ()
{
}
/**
* Falls die Option Start ausgewaehlt ist, wird zum
* Spieltypauswahlmenu gewechselt. Ist die Option Exit ausgewaehlt,
* wird zurueck zum Handyhauptmenue gekehrt.
*/
public void fireKeyPressed ()
{
i
= 0;
isMenuActive = false;
if (currentStatus == START_GAME)
{
controller.setDisplayable3D (controller.getMenuGameType ());
}
else if (currentStatus == EXIT_GAME)
{
controller.exitGame ();
}
else
{
System.err.println ("Unknown option error in class Menu" +
"GameStart!");
};
}
/**
* Zeigt ein Bild bei Spielstart an, das Mobile Reversi 3D by Mehmet
* Akin beinhaltet, dass den Namen des Spiels bekannt macht.
*/
public void showGameLoadDisplay ()
{
Seite 137 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Background backGround = new Background ();
try
{
backGround.setImage (new Image2D (Image2D.RGB,
Image.createImage ("/res/images/gameStart.png")));
} catch (IOException e)
{
e.printStackTrace ();
};
g3d.bindTarget (g);
g3d.clear (backGround);
g3d.releaseTarget ();
flushGraphics ();
}
/**
* Falls die Option Start ausgewaehlt ist, wird zum
* Spieltypauswahlmenu gewechselt. Ist die Option Exit ausgewaehlt,
* wird zurueck zum Handyhauptmenue gekehrt.
*/
public void upKeyPressed ()
{
i
= 0;
currentStatus = (currentStatus == START_GAME)
? EXIT_GAME : START_GAME;
createBackground ();
}
}
MenuGameType.java
package menu3d;
import main.GameController;
import network.OpponentChooser;
import player.PlayerHuman;
import reversi.Board;
import javax.microedition.m3g.Mesh;
import javax.microedition.m3g.Transform;
/**
* Menue zur Auswahl des Spieltyps. Die drei Optionen, die gewaehlt
* werden koennen sind 1P vs 2P fuer ein Spiel zwischen zwei
* menschlichen Spielern, 1P vs Com fuer ein Spiel zwischen einem
* menschlichen Spieler und dem Handy mit der KI sowie 1P vs Net fuer
* ein Netwerkspiel zwischen zwei entfernten Spielern.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;MenuGameType.java<br>
* <b>Date:</b><br>
* <b>History:</b>21.10.2006<br>
*
25.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Kommandozeilenbasiertes auswaehlen der Optionen durch
*
3D-Untermenue ersetzt<br>
*
* @author Mehmet Akin
* @version 1.1
*/
public class MenuGameType extends Menu3D
{
Seite 138 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
/**
* Option fuer ein Spiel zwischen einem menschlichen Spieler und
* dem Handy mit der kuenstlichen Intelligenz
*/
private static final int PLAYER_VS_AI
= 0;
/**
* Option fuer ein Spiel zwischen zwei menschlichen Spielern
*/
private static final int PLAYER_VS_PLAYER = 1;
/**
* Option fuer ein Netwerkspiel zwischen zwei entfernten menschlichen
* Spielern
*/
private static final int PLAYER_VS_NETWORK = 2;
/**
* Initialisiert das Spieltypauswahlmenue
*
* @param controller Spielsteuerung, die Tastenbetaetigungen des
*
Spielers an das Menue weiter leitet
*/
public MenuGameType (GameController controller)
{
super (controller);
Transform transform = new Transform ();
createMenu (menu3dLoader.getMenuGameType ());
options
= new Mesh [3];
options [PLAYER_VS_AI]
=
menu3dLoader.getGameTypeOption1PVsCom ();
options [PLAYER_VS_PLAYER] =
menu3dLoader.getGameTypeOption1PVs2P ();
options [PLAYER_VS_NETWORK] =
menu3dLoader.getGameTypeOption1PVsNet ();
/* Verschiebe die Optionen so, wie sie auf der Anzeige dargestellt
* werden sollen
*/
transform.postTranslate (0, 23, 0);
options [PLAYER_VS_AI].setTransform (transform);
transform.setIdentity ();
transform.postTranslate (0, -5, 0);
options [PLAYER_VS_PLAYER].setTransform (transform);
transform.setIdentity ();
transform.postTranslate (0, -33, 0);
options [PLAYER_VS_NETWORK].setTransform (transform);
createBackground ();
currentStatus = PLAYER_VS_AI;
}
/**
* Wechselt zu der Option, die sich unter der aktuell augewaehlten
* befindet. Ist die Option 1P vs Net aktuell ausgewaehlt, die sich
* auf der Anzeige ganz unten befindet, dann wird zur Option 1P vs 2P
* ganz oben gewechselt.
*/
public void downKeyPressed ()
{
Seite 139 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
i = 0;
if (currentStatus == PLAYER_VS_PLAYER)
{
currentStatus = PLAYER_VS_NETWORK;
}
else if (currentStatus == PLAYER_VS_NETWORK)
{
currentStatus = PLAYER_VS_AI;
}
else if (currentStatus == PLAYER_VS_AI)
{
currentStatus = PLAYER_VS_PLAYER;
}
else
{
System.err.println ("Unknown option error in class Menu" +
"GameType!");
};
createBackground ();
}
/**
* Fuehrt dazu, dass das Menue fuer die Spieltypauswahl verlassen und
* zum Startmenue, von dem aus es aufgerufen wurde zurueck gekehrt
* wird.
*/
public void exitKeyPressed ()
{
isMenuActive = false;
controller.setDisplayable3D (controller.getMenuGameStart ());
}
/**
* Falls die Option 1P vs 2P ausgewaehlt ist, wird ein Spiel zwischen
* zwei menschlichen Spielern direkt gestartet. Ist die Option 1P vs
* Com wird zum Untermenue zur Auswahl der Schwierigkeitsstufe
* gewechselt. Bei der Option 1P vs Net erscheint ein Formular zum
* Einwaehlen auf dem zentralen GameServer im Internet.
*/
public void fireKeyPressed ()
{
i
= 0;
isMenuActive = false;
if (currentStatus == PLAYER_VS_PLAYER)
{
controller.startGame (new PlayerHuman (Board.WHITE_PLAYER),
new PlayerHuman (Board.BLACK_PLAYER));
}
else if (currentStatus == PLAYER_VS_NETWORK)
{
new OpponentChooser (controller);
}
else if (currentStatus == PLAYER_VS_AI)
{
controller.setDisplayable3D (controller.getMenuGameLevel ());
}
else
{
Seite 140 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
System.err.println ("Unknown option error in class Menu" +
"GameType!");
};
}
/**
* Wechselt zu der Option, die sich ueber der aktuell augewaehlten
* befindet. Ist die Option 1P vs 2P aktuell ausgewaehlt, die sich
* auf der Anzeige ganz oben befindet, dann wird zur Option 1P vs Net
* ganz unten gewechselt.
*/
public void upKeyPressed ()
{
i = 0;
if (currentStatus == PLAYER_VS_PLAYER)
{
currentStatus = PLAYER_VS_AI;
}
else if (currentStatus == PLAYER_VS_NETWORK)
{
currentStatus = PLAYER_VS_PLAYER;
}
else if (currentStatus == PLAYER_VS_AI)
{
currentStatus = PLAYER_VS_NETWORK;
}
else
{
System.err.println ("Unknown option error in class Menu" +
"GameType!");
};
createBackground ();
}
}
Paket player
Player.java
package player;
import main.Game;
/**
* Basisklasse fuer alle Spielertypen, die es gibt. Liefert Funktionen
* zum Setzen eines Steines sowie zum Behandeln von Situation, in denen
* der Spieler nicht setzen kann.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;Player.java<br>
* <b>Date:</b>22.10.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
public abstract class Player
{
/**
* Farbe des Spielers. Farbe der Steinfarbe, die der Spieler setzt.
*/
Seite 141 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
protected byte color;
/**
* Das Spiel. Wird benachrichtigt, wenn ein Spieler nicht setzen kann
* oder das Spiel zu Ende ist.
*/
protected Game game;
/**
* Initialisiert den Spieler.
*
* @param color Farbe der Steine, die der Spieler setzt.
*/
public Player (byte color)
{
this.color = color;
}
/**
* Setzt den naechsten Zug, den der Spieler macht.
*/
public abstract void doNextSet ();
/**
* Dient dazu den Bildschirm fuer den Spieler freizugeben, damit
* dieser ein Spielfeld auswaehlen kann.
*
* @return true, wenn der Spieler Zugriff auf das Spielbrett haben
*
soll und ein Feld auswaehlen kann, ansonsten false
*/
public abstract boolean handlePlayerChanged ();
/**
* Dient zum Behandeln von Spielsituation, in denen der Spieler nicht
* setzen kann und aussetzen muss.
*/
public abstract void notifyHasToStay ();
/**
* Setzt das Spiel-Objekt fuer diesen Spieler, damit dieser das Spiel
* informieren kann, wenn er gesetzt hat und das Spiel den anderen
* Spieler benachrichtigen kann, zu setzen.
*
* @param game Spiel, dass die einzelnen Spiele zum Setzen
*
benachrichtigt
*/
public void setGame (Game game)
{
this.game = game;
}
/**
* Liefert die Farbe der Steine zurueck, die der Spieler setzt.
*
* @return Farbe der Steine des Spielers
*/
public byte getColor ()
{
return color;
}
}
Seite 142 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
PlayerAI.java
package player;
import ai.Intelligence;
import reversi.Reversi;
/**
* Stellt die kuenstliche Intelligenz dar, der den Zug berechnet
* und automatisch setzt. Dazu benutzt die Klasse abgeleitete Klassen
* von ai.Intelligence.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;PlayerAI.java<br>
* <b>Date:</b>22.10.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
public class PlayerAI extends Player
{
/**
* Kuensliche Intelligenz, die der Spieler zur Berechnung des an die
* Schwierigkeitsstufe angepassten Zuges verwendet.
*/
private Intelligence intelligence;
/**
* Initialisiert den Spieler mit der kuenstlichen Intelligenz.
*
* @param color Farbe des Steines, das der Spiele setzen soll
* @param intelligence Schwierigkeitsstufe der kuenstlichen
*
Intelligenz
*/
public PlayerAI (byte color, Intelligence intelligence)
{
super (color);
this.intelligence = intelligence;
}
/**
* Berechnet den besten fuer die Schwierigkeitsstufe gegebeben Zug.
* Prueft danach ob der andere Spieler aussetzen muss und setzt falls
* ja, noch einen Zug. Falls auch dieser Spieler aussetzen muss,
* wird das Spiel benachricht, das Spielende auszuwerten.
*/
public void doNextSet ()
{
Reversi reversi = game.getReversi ();
/* Verzoegerung, damit die kuenstliche Intelligenz nicht zu schnell
* den Zug durchfuehrt
*/
try
{
Thread.sleep (1000);
} catch (InterruptedException e)
{
e.printStackTrace ();
};
Seite 143 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
intelligence.searchForBestPosition (reversi.getBoard ().getBoard (),
color);
reversi.set (intelligence.getBestPositionX (),
intelligence.getBestPositionY (), color,
intelligence.getValidDirections ());
if (reversi.hasToStay (reversi.getOtherColor (color)))
{
if (reversi.hasToStay (color))
{
/* Wenn beide Spieler aussetzen muessen, ist das Spiel zu Ende*/
game.evaluateGameEnd ();
}
else
{
handleHasToStay ();
game.handleOtherPlayerHasToStay ();
};
}
else
{
game.changePlayer ();
};
}
/**
* Dient dazu noch einen Zug zu machen, wenn der gegnerische
* menschliche Spieler aussetzen muss.
*/
public void handleHasToStay ()
{
doNextSet ();
}
/**
* @see Player#handlePlayerChanged()
*/
public boolean handlePlayerChanged ()
{
doNextSet ();
return true;
}
/**
* @see Player#notifyHasToStay()
*/
public void notifyHasToStay ()
{
}
}
PlayerHuman.java
package player;
import reversi.Reversi;
/**
* Stellt einen menschlichen Spieler dar. Der Spieler macht einen Zug,
* indem er mit den Handytasten das Feld auswaehlt auf dem er setzen
* will und dann die Feuertaste drueckt.<br><br>
*
Seite 144 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* <b>File:</b>&nbsp;&nbsp;PlayerHuman.java<br>
* <b>Date:</b>22.10.2006<br>
* <b>History:</b><br>
*
06.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Funktion doNextSet implementiert<br>
*
* @author Mehmet Akin
* @version 1.1
*/
public class PlayerHuman extends Player
{
/**
* Initialisiert den Spieler.
*
* @param color Farbe des Steines, das der Spiele setzen soll
*/
public PlayerHuman (byte color)
{
super (color);
}
/**
* Fuehrt den Zug fuer das vom Spieler ausgewaehlte Feld. Prueft
* zuerst ob der Gegner setzen kann. Wenn nicht, macht er den Zug.
* Wenn der Gegner aussetzen muss und der Spieler auch, wird das
* Spiel benachricht das Spielende auszuwerten.
*/
public void doNextSet ()
{
Reversi
reversi
= game.getReversi ();
byte [][] validDirections = reversi.getValidDirections (color);
if (validDirections.length == 0)
{
game.getController ().getSoundPlayer ().playSoundErrorSet ();
game.handleSetNotValid ();
}
else
{
reversi.set (color, validDirections);
if (reversi.hasToStay (reversi.getOtherColor (color)))
{
if (reversi.hasToStay (color))
{
/* Wenn beide Spieler aussetzen muessen, ist das Spiel zu
* Ende
*/
game.handleOtherPlayerHasToStay ();
game.evaluateGameEnd ();
}
else
{
handleHasToStay ();
game.handleOtherPlayerHasToStay ();
};
}
else
{
game.changePlayer ();
};
Seite 145 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
};
}
/**
* Falls der gegnerische Spieler aussetzen muss, so wird die Anzeige
* fuer den Spieler sofort wieder freigegeben, damit dieser noch einen
* Zug machen kann.
*/
public void handleHasToStay ()
{
game.resetCanvasListener ();
}
/**
* @see Player#handlePlayerChanged()
*/
public boolean handlePlayerChanged ()
{
return true;
}
/**
* @see Player#notifyHasToStay()
*/
public void notifyHasToStay ()
{
}
}
PlayerNetwork.java
package player;
import network.PlayerConnector;
import reversi.Reversi;
/**
* Simuliert einen entfernten Spieler lokal. Macht einen Zug indem
* der Zug vom entfernten Spieler empfangen wird und lokal ausgefuehrt
* wird. Wenn lokal ein Zug gemacht wird, wird dieser entsprechend
* an den enfernten Spieler gesendet. Zum Senden und Empfangen von Daten
* wird der PlayerConnector benutzt.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;PlayerNetwork.java<br>
* <b>Date:</b>22.10.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
public class PlayerNetwork extends Player
{
/**
* Schnittstelle zum Senden und Empfangen von Daten
*/
private PlayerConnector connector;
/**
* Initialisiert den Netwerkspieler.
*
* @param color Farbe der Steine, die der entfernte Spieler setzt
* @param connector Schnittstelle zum Senden und Empfangen von Daten
*/
Seite 146 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
public PlayerNetwork (byte color, PlayerConnector connector)
{
super (color);
this.connector = connector;
}
/**
* Sendet den Zug, den der menschliche Spieler lokal ausgefuehrt hat,
* zu dem entfernten Spieler.
*/
public void doNextSet ()
{
Reversi reversi = game.getReversi ();
connector.sendMove (reversi.getLastPositionXSet (),
reversi.getLastPositionYSet ());
}
/**
* @see Player#handlePlayerChanged()
*/
public boolean handlePlayerChanged ()
{
doNextSet ();
return false;
}
/**
* Sendet ueber die Schnittstelle PlayerConnector eine Nachricht
* zum Server zum Abbau der Verbindung.
*/
public void logout ()
{
connector.logout ();
}
/**
* @see Player#notifyHasToStay()
*/
public void notifyHasToStay ()
{
doNextSet ();
}
/**
* Kehrt vom Spiel zurueck zum Spieltypauswahlmenue
*/
public void resetGame ()
{
game.resetGame ();
}
/**
* Setzt einen Stein auf der entsprechenden Position.
*
* @param positionX x-Koordinate des Zugs
* @param positionY y-Koordinate des Zugs
*/
public void set (byte positionX, byte positionY)
{
Reversi reversi = game.getReversi ();
Seite 147 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
reversi.set (positionX, positionY, color,
reversi.getValidDirections (positionX, positionY,
color));
if (reversi.hasToStay (reversi.getOtherColor (color)))
{
if (reversi.hasToStay (color))
{
game.evaluateGameEnd ();
}
else
{
game.handleOtherPlayerHasToStay ();
};
}
else
{
game.changePlayer ();
};
}
}
Paket reversi
Board.java
package reversi;
import java.util.Vector;
/**
* Die interne Darstellung des Spielbretts. Stellt Funktionen zum
* Setzen eines Steins auf dem Brett zur Verfuegung. Jedoch prueft es
* nicht, ob der Zug gueltig ist. Die Gueltigkeit wird von der Klasse
* Reversi geprueft.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;Board.java<br>
* <b>Date:</b>22.10.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
public class Board
{
/**
* Wertzuordnung fuer den weissen Spieler. Leichter zu behandeln als
* eine Zeichenkette und wird zur Berechnung der Brettbewertung von
* den Intelligence-Klassen benoetigt
*/
public
static final byte
WHITE_PLAYER = 1;
/**
* Maximale Feldanzahl in y-Richtung
*/
public
static final byte
MAX_Y
= 6;
/**
* Maximale Feldanzahl in x-Richtung
*/
public
static final byte
MAX_X
= 6;
/**
* Feldbewertung fuer ein leeres Feld
Seite 148 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
public
static final byte
EMPTY
= 0;
/**
* Feldbewertung fuer ein Feld, auf dem ein schwarzer Stein platziert
* ist
*/
public
static final byte
BLACK_PLAYER = -1;
/**
* Interne Darstellung des Spilbretts
*/
protected byte [][]
board;
/**
* Liste aller Koordinaten, die beim Setzen des Steins an der
* jeweiligen Position in die gegnerische Farbe gedreht wurden
*/
private
Vector
coordinatesDeleted;
/**
* Koordinaten, um das Spielfeld in Richtung Westen zu durchlaufen
*/
private
static final byte []
WEST
= new byte [] {-1, 0};
/**
* Koordinaten, um das Spielfeld in Richtung Sued-Westen zu durchlaufen
*/
private
static final byte []
SOUTH_WEST
= new byte [] {-1, -1};
/**
* Koordinaten, um das Spielfeld in Richtung Sued-Osten zu durchlaufen
*/
private
static final byte []
SOUTH_EAST
= new byte [] { 1, -1};
/**
* Koordinaten, um das Spielfeld in Richtung Sueden zu durchlaufen
*/
private
static final byte []
SOUTH
= new byte [] { 0, -1};
/**
* Koordinaten, um das Spielfeld in Richtung Nord-Westen zu
* durchlaufen
*/
private
static final byte []
NORTH_WEST
= new byte [] {-1, 1};
/**
* Koordinaten, um das Spielfeld in Richtung Nord-Osten zu durchlaufen
*/
private
static final byte []
NORTH_EAST
= new byte [] { 1, 1};
/**
* Koordinaten, um das Spielfeld in Richtung Norden zu durchlaufen
*/
private
static final byte []
NORTH
= new byte [] { 0, 1};
/**
* Koordinaten, um das Spielfeld in Richtung Osten zu durchlaufen
*/
private
static final byte []
EAST
= new byte [] { 1, 0};
/**
* Zusammenfassung aller Richtungen, in denen Steine gedreht werden
* koennen
*/
public
static final byte [][] DIRECTIONS
= new byte [][]
{
NORTH, NORTH_EAST, EAST, SOUTH_EAST, SOUTH, SOUTH_WEST, WEST,
NORTH_WEST
};
/**
* Initialisiert das interne Spielbrett. Das Reversi-Spiel beginnt
Seite 149 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* mit der Anfangsaufstellung: (2,2) und (3,3) weisser Spieler,
* (2,3) und (3,2) schwarzer Spieler.
*/
public Board ()
{
board
= new byte [MAX_X][MAX_Y];
for (byte i = 0; i < MAX_X; i++)
{
for (byte j = 0; j < MAX_Y; j++)
{
board [i][j] = EMPTY;
};
};
board
board
board
board
[2][2]
[3][3]
[2][3]
[3][2]
=
=
=
=
WHITE_PLAYER;
WHITE_PLAYER;
BLACK_PLAYER;
BLACK_PLAYER;
}
/**
* Gibt das Spielbrett als Zeichenkette auf der Kommandozeile aus.
*/
public String toString ()
{
String boardStr = "";
for (byte i = MAX_Y - 1; i >= 0; i--)
{
for (byte j = 0; j < MAX_X; j++)
{
if (board [j][i] == BLACK_PLAYER)
{
boardStr += "B ";
}
else if (board [j][i] == WHITE_PLAYER)
{
boardStr += "W ";
}
else
{
boardStr += "O ";
};
};
boardStr += "\n";
};
return boardStr;
}
/**
* Liefert die interne Darstellung des Spielbretts.
*
* @return Spielbrett als 2-dimensionales Feld
*/
public byte [][] getBoard ()
{
return board;
}
Seite 150 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
/**
* Liefert die Koordinaten der Steine, die beim Durchfuehren des Zuges
* gedreht wurden.
*
* @return Koordinaten der Steine, die gedreht wurden
*/
public byte [][] getCoordinatesDeleted ()
{
int
indicesToDeleteSize = coordinatesDeleted.size ();
byte [][] indicesToDelete
= new byte [indicesToDeleteSize][2];
for (byte i = 0; i < indicesToDeleteSize; i++)
{
indicesToDelete [i] = (byte []) coordinatesDeleted.elementAt (i);
};
return indicesToDelete;
}
/**
* Liefert eine Zahl, die angibt welcher Spieler mehr Steine auf
* dem Spielbrett besitzt. Wird am Ende Spiels von der Klasse Game
* aufgerufen, um zu entscheiden welcher Spieler gewonnen hat oder ob
* es sich um ein Unentschieden handelt.
*
* @return Zahl groesser 0, wenn der weisse Spieler gewonnen hat.
*
Zahl kleiner 0, wenn der schwarze Spieler gewonnen hat.
*
0, wenn das Spiel im Unentschieden endet.
*/
public int getWinNumber ()
{
int number = 0;
for (int i = 0; i < MAX_X; i++)
{
for (int j = 0; j < MAX_Y; j++)
{
if (board [i][j] == WHITE_PLAYER)
{
++number;
};
if (board [i][j] == BLACK_PLAYER)
{
--number;
};
};
};
return number;
}
/**
* Gibt an, ob sich eine Koordinate ausserhalb des Spielbretts
* befindet.
*
* @param x x-Koordinate, fuer die geprueft werden soll
* @param y y-Koordinate, fuer die geprueft werden soll
* @return true, wenn Koordinate innerhalb der Spielgrenzen,
*
ansonsten false
Seite 151 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
public static boolean isPositionOutOfBounds (int x, int y)
{
return (x >= MAX_X) || (x < 0) || (y >= MAX_Y) || (y < 0);
}
/**
* Setzt einen Stein an der gegebenen Koordinate und dreht alle
* geschlagenen Steine in die gegnerische Farbe.
*
* @param x x-Koordinate des Zugs
* @param y y-Koordinate des Zugs
* @param colorToSet Farbe des Steins, der gesetzt werden soll
* @param directions Richtugen, in denen Steine gedreht werden muessen
*/
public void set (byte x, byte y, byte colorToSet,
byte [][] directions)
{
byte colorToTurn = (colorToSet == WHITE_PLAYER)
? BLACK_PLAYER : WHITE_PLAYER;
board [x][y] = colorToSet;
int directionsLength = directions.length;
coordinatesDeleted = new Vector ();
for (byte i = 0; i < directionsLength; i++)
{
for (byte k = 1; ; k++)
{
int xTemp = x + k * directions [i][0];
int yTemp = y + k * directions [i][1];
byte color = board [xTemp][yTemp];
if (color == colorToTurn)
{
board [xTemp][yTemp] = colorToSet;
coordinatesDeleted.addElement (new byte [] {(byte) xTemp,
(byte) yTemp});
}
else
{
break;
};
};
};
}
/**
* Setzt die Farbe eines Steins in die entsprechende Farbe.
*
* @param x x-Koordinate des Steins
* @param y y-Koordinate des Steins
* @param color Farbe, die der Stein erhalten soll
*/
public void setPosition (byte x, byte y, byte color)
{
board [x][y] = color;
}
Seite 152 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
}
Reversi.java
package reversi;
import graphics3d.Board3D;
import main.GameController;
import sound.SoundPlayer;
/**
* Representiert das Reversi-Spiel mit seinem Spielbrett und den
* Spielregeln und verbindet diese.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;Reversi.java<br>
* <b>Date:</b>23.10.2006<br>
* <b>History:</b><br>
*
07.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
2D zu 3D Mapper Variable hinzu gefuegt<br>
*
06.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Funktion hasToStay hinzu gefuegt<br>
*
* @author Mehmet Akin
* @version 1.2
*/
public class Reversi
{
/**
* Interne Darstellung des Spielbretts
*/
private Board
board;
/**
* Grafische Representation des internen Spielbretts
*/
private Board3D
board3D;
/**
* x-Koordinate des zuletzt durchgefuehrten Zuges
*/
private byte
lastPositionXSet;
/**
* y-Koordinate des zuletzt durchgefuehrten Zuges
*/
private byte
lastPositionYSet;
/**
* Ordnet eine 2D-Koordinate den entsprechenden Index zu, mit dem
* er auf dem 3D-Spielbrett zugreifbar ist
*/
private byte [][]
position2DTo3DMapper;
/**
* Zaehler, der hochgezaehlt wird, wenn ein Stein gesetzt
*/
private byte
position2Dto3DCounter;
/**
* x-Koordinate des aktuell ausgewaehlten Spielfeldes
*/
private byte
positionX;
/**
* y-Koordinate des aktuell ausgewaehlten Spielfeldes
*/
private byte
positionY;
/**
* Regeln, nach denen das Spiel gespielt wird
Seite 153 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
private Rules
rules;
/**
* Dient zum Abspielen von Audiodateien, wenn ein gueltiger Zug
* durchgefuehrt wird oder ein ungueltiges Feld ausgewaehlt wird
*/
private SoundPlayer soundPlayer;
/**
* Initialisiert das Reversi-Objekt.
*
* @param controller Spielsteuerung zur Weiterleitung von
*
Tastenbetaetingungen
*/
public Reversi (GameController controller)
{
board
= new Board ();
board3D
= new Board3D (controller);
rules
= new Rules ();
soundPlayer
= controller.getSoundPlayer ();
positionX
= 4;
positionY
= 3;
position2DTo3DMapper
= new byte [6][6];
position2Dto3DCounter
= 3;
position2DTo3DMapper [2][2] = 0;
position2DTo3DMapper [3][3] = 1;
position2DTo3DMapper [2][3] = 2;
position2DTo3DMapper [3][2] = 3;
}
/**
* Fuehrt dazu dass das Feld unter dem aktuell ausgewaehlten Feld
* in Richtung der negativen y-Achse das aktuell ausgewaehlte wird.
*/
public void moveDown ()
{
if (positionY > 0)
{
positionY -= 1;
board3D.moveDown ();
};
}
/**
* Fuehrt dazu dass das Feld links neben dem aktuell ausgewaehlten
* Feld in Richtung der negativen x-Achse das aktuell ausgewaehlte
* wird.
*/
public void moveLeft ()
{
if (positionX > 0)
{
positionX -= 1;
board3D.moveLeft ();
};
}
/**
* Fuehrt dazu dass das Feld rechts neben dem aktuell ausgewaehlten
Seite 154 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* Feld in Richtung der positiven x-Achse das aktuell ausgewaehlte
* wird.
*/
public void moveRight ()
{
if (positionX < Board.MAX_X - 1)
{
positionX += 1;
board3D.moveRight ();
};
}
/**
* Fuehrt dazu dass das Feld ueber dem aktuell ausgewaehlten Feld
* in Richtung der positiven y-Achse das aktuell ausgewaehlte wird.
*/
public void moveUp ()
{
if (positionY < Board.MAX_Y - 1)
{
positionY += 1;
board3D.moveUp ();
};
}
/**
* Liefert die interne Darstellung des Spielbretts.
*
* @return Interne Darstellung des Spielbretts
*/
public Board getBoard ()
{
return board;
}
/**
* Liefert die grafische Darstellung des internen Spielfeldes
*
* @return 3D-Spielfeld, das auf der Anzeige gerendert wird
*/
public Board3D getBoard3D ()
{
return board3D;
}
/**
* Liefert die x-Koordinate des Zugs, der als letztes ausgefuehrt
* wurde.
*
* @return x-Koordinate des zuletzt ausgefuehrten Zuges
*/
public byte getLastPositionXSet ()
{
return lastPositionXSet;
}
/**
* Liefert die y-Koordinate des Zugs, der als letztes ausgefuehrt
* wurde.
Seite 155 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*
* @return y-Koordinate des zuletzt ausgefuehrten Zuges
*/
public byte getLastPositionYSet ()
{
return lastPositionYSet;
}
/**
* Liefert die gegnerische Farbe der uebergebenen Farbe
*
* @param color Farbe, fuer die die gegnerische Farbe bestimmt werden
*
soll
* @return WHITE_PLAYER, wenn Farbe BLACK_PLAYER, sonst BLACK_PLAYER
*/
public byte getOtherColor (byte color)
{
return (color == Board.WHITE_PLAYER)
? Board.BLACK_PLAYER : Board.WHITE_PLAYER;
}
/**
* Liefert die x-Koordinate des aktuell ausgewaehlten Feldes.
*
* @return x-Koordinate des aktuell gewaehlten Feldes
*/
public byte getPositionX ()
{
return positionX;
}
/**
* Liefert die y-Koordinate des aktuell ausgewaehlten Feldes.
*
* @return y-Koordinate des aktuell gewaehlten Feldes
*/
public byte getPositionY ()
{
return positionY;
}
/**
* Liefert die Richtungen, in denen Steine gedreht werden koennen,
* wenn der Spieler einen Stein auf positionX,positionY setzt.
*
* @param colorToSet Farbe des Spielers, fuer den die Richtungen
*
bestimmt werden sollen, in denen diese Steine des Gegners
*
drehen kann
* @return Richtungen, in denen Steine der gegnerischen Farbe gedreht
*
werden koennen
*/
public byte [][] getValidDirections (byte colorToSet)
{
return getValidDirections (positionX, positionY, colorToSet);
}
/**
* Liefert die Richtungen, in denen Steine gedreht werden koennen,
* wenn der Spieler einen Stein auf x,y setzt.
*
* @param x x-Koordinate des Zuges
Seite 156 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* @param y y-Koordinate des Zuges
* @param colorToSet Farbe des Spielers, fuer den die Richtungen
*
bestimmt werden sollen, in denen diese Steine des Gegners
*
drehen kann
* @return Richtungen, in denen Steine der gegnerischen Farbe gedreht
*
werden koennen
*/
public byte [][] getValidDirections (byte x, byte y, byte colorToSet)
{
return rules.getValidDirections (board.getBoard (), x, y,
colorToSet);
}
/**
* Liefert die Zahl, die angibt welcher Spieler mehr Steine in einer
* gegebenen Spielsituation hat. Wird bei Spielende von der Klasse
* Game aufgerufen.
*
* @return Zahl groesser 0, wenn der weisse Spieler gewonnen hat.
*
Zahl kleiner 0, wenn der schwarze Spieler gewonnen hat.
*
0 bei Untentschieden
*/
public int getWinNumber ()
{
return board.getWinNumber ();
}
/**
* Prueft, ob ein Spieler aussetzen muss
*
* @param colorToStay Farbe, fuer den geprueft werden soll
* @return true, wenn der Spieler mit der Farbe colorToStay aussetzen
*
muss, ansonsten false
*/
public boolean hasToStay (byte colorToStay)
{
return rules.hasToStay (board.getBoard (), colorToStay);
}
/**
* Setzt den Stein in der entsprechenden Farbe und dreht gegnerische
* Steine in alle moeglichen Richtungen.
*
* @param colorToSet Farbe des Steins, das gesetzt werden sollS
* @param directions Richtungen, in denen Steine der gegnerischen
*
Farbe gedreht werden muessen
*/
public void set (byte colorToSet, byte [][] directions)
{
set (positionX, positionY, colorToSet, directions);
}
/**
* Setzt den Stein in der entsprechenden Farbe und dreht gegnerische
* Steine in alle moeglichen Richtungen.
*
* @param x x-Koordinate des Zugs
* @param y y-Koordinate des Zugs
* @param colorToSet Farbe des Steins, das gesetzt werden sollS
* @param directions Richtungen, in denen Steine der gegnerischen
*
Farbe gedreht werden muessen
Seite 157 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
public void set (byte x, byte y, byte colorToSet,
byte [][] directions)
{
soundPlayer.playSoundSet ();
lastPositionXSet = x;
lastPositionYSet = y;
board.set (x, y, colorToSet, directions);
position2DTo3DMapper [x][y] = ++position2Dto3DCounter;
byte [][] coordinatesDeleted = board.getCoordinatesDeleted ();
int
indicesLength
= coordinatesDeleted.length;
byte []
indicesToDelete
= new byte [indicesLength];
for (byte i = 0; i < indicesLength; i++)
{
indicesToDelete [i] = position2DTo3DMapper [
coordinatesDeleted [i][0]][coordinatesDeleted [i][1]];
};
board3D.set (x, y, colorToSet, indicesToDelete, coordinatesDeleted);
}
}
Rules.java
package reversi;
import java.util.Vector;
/**
* Spielregeln, nach denen Reversi gespielt wird. Stellt Funktionen
* zur Verfuegung mit denen die Gueltigkeit eines Zuges fuer eine
* bestimmte Spielerfarbe bestimmt werden kann oder ob ein Spieler
* in einer gegebenen Spielsituation keine Zugmoeglichkeit hat und
* aussetzen muss.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;Rules.java<br>
* <b>Date:</b>22.10.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
public class Rules
{
/**
* Liefert alle Richtungen, in denen beim Setzen eines Steins einer
* bestimmten Farbe auf einem Spielfeld Steine des gegnerischen
* Spielers gedreht werden koennen. Wenn die Anzahl der Richtungen
* 0 ist, dann heisst das, dass der Zug nicht gueltig ist und somit
* nicht gesetzt werden kann.
*
* @param board Brett, auf dem ueberprueft wird, ob der Zug gueltig
*
ist
* @param x x-Koordinate des Zugs, das ueberprueft werden soll
* @param y y-Koordinate des Zugs, das ueberprueft werden soll
* @param colorToSet Farbe des Steins, das gesetzt werden soll
* @return Anzahl der Richtungen in denen Steine des Gegners gedreht
*
werden koennen
Seite 158 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
public byte [][] getValidDirections (byte [][] board, byte x, byte y,
byte colorToSet)
{
byte
colorToTurn
= (colorToSet == Board.WHITE_PLAYER)
? Board.BLACK_PLAYER
: Board.WHITE_PLAYER;
Vector
directions
= new Vector ();
int
directionsLength = Board.DIRECTIONS.length;
byte []
direction;
int
vecDirectionsLength;
byte [][] directionsToReturn;
if (board [x][y] != Board.EMPTY)
{
return new byte [][]
{
};
};
for (byte i = 0; i < directionsLength; i++)
{
direction = Board.DIRECTIONS [i];
int xTemp = x + direction [0];
int yTemp = y + direction [1];
byte color;
if (!Board.isPositionOutOfBounds (xTemp, yTemp))
{
color = board [xTemp][yTemp];
if ((color == Board.EMPTY) || (color != colorToTurn))
{
continue;
};
}
else
{
continue;
};
for (byte j = 2; ; j++)
{
xTemp = x + j * direction [0];
yTemp = y + j * direction [1];
if (!Board.isPositionOutOfBounds (xTemp, yTemp))
{
color = board [xTemp][yTemp];
if (color == colorToSet)
{
directions.addElement (direction);
}
else
{
if (color == Board.EMPTY)
{
break;
};
Seite 159 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
};
}
else
{
break;
};
};
};
vecDirectionsLength = directions.size ();
directionsToReturn = new byte [vecDirectionsLength][2];
for (byte i = 0; i < vecDirectionsLength; i++)
{
directionsToReturn [i] = (byte []) directions.elementAt (i);
};
return directionsToReturn;
}
/**
* Ueberprueft, ob ein Spieler bei gegebener Spielsituation aussetzen
* muss. Um nicht unnoetig die Gueltigkeit auf ein Feld zu setzen zu
* pruefen, indem das ganze Spielbrett durchlaufen wird, wird
* stattdessen nur an den angrenzenden Feldern zu den Steinen
* mit der gegnerischen Farbe geprueft, auf denen es moeglich waere
* zu setzen. Das ist sehr viel schneller als jedes Feld auf
* Zuggueltigkeit zu pruefen.
*
* @param board Spielbrett, auf dem geprueft wird, ob der Spieler
*
einer Farbe aussetzen muss
* @param colorToStay Farbe des Spielers, fuer den die
*
Aussetzmoeglichkeit berechnet wird
* @return true, wenn der Spieler mit der Farbe colorToStay auf dem
*
Spielbrett board aussetzen muss, ansonsten false
*/
public boolean hasToStay (byte [][] board, byte colorToStay)
{
byte
colorNotToStay = (colorToStay == Board.WHITE_PLAYER)
? Board.BLACK_PLAYER
: Board.WHITE_PLAYER;
byte
color;
boolean [][] checkedBoard
= new boolean [6][6];
int
directionsLength = Board.DIRECTIONS.length;
byte []
direction;
boolean
emptyOccured = false;
for (byte i = 0; i <
{
for (byte j = 0; j
{
if (board [i][j]
{
emptyOccured =
};
};
};
Board.MAX_Y; i++)
< Board.MAX_X; j++)
== Board.EMPTY)
true;
if (!emptyOccured)
{
return true;
Seite 160 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
};
for (byte i = 0; i < Board.MAX_Y; i++)
{
for (byte j = 0; j < Board.MAX_X; j++)
{
color = board [i][j];
if (color == colorNotToStay)
{
for (byte k = 0; k < directionsLength; k++)
{
direction = Board.DIRECTIONS [k];
byte tempX = (byte) (i + direction [0]);
byte tempY = (byte) (j + direction [1]);
if ((!Board.isPositionOutOfBounds (tempX, tempY)) &&
(board [tempX][tempY] == Board.EMPTY) &&
(checkedBoard [tempX][tempY] == false))
{
checkedBoard [tempX][tempY] = true;
if ((getValidDirections (board, tempX, tempY,
colorToStay)).length > 0)
{
return false;
};
};
};
};
};
};
return true;
}
}
Paket server
GameServer.java
package server;
import java.io.*;
import java.net.*;
/**
* Verwaltet alle Verbindungen zu den einzelnen Spielern. Es koennen
* sich maximal 30 Spieler einwaehlen. Fuegt neue Spieler zu den
* verfuegbaren hinzu und kann auch Spieler von dieser Liste wieder
* entfernen. Ueber die Kommandozeile koennen alle Spieler angezeigt
* werden, die eingewaehlt sind oder der Server beendet werden. Fuer
* jeden Spieler wird ein PlayerLinker-Objekt erzeugt, dass die
* Schnittstelle zum Server darstellt und Daten vom und zum Spieler
* senden und empfangen kann.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;GameServer.java<br>
* <b>Date:</b>10.11.2006<br>
*
* @author Mehmet Akin
Seite 161 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* @version 1.0
*/
public class GameServer
{
/**
* Falls kein Port als Argument uebergeben wird, wird dieser Port
* genommen
*/
private static final int
DEFAULT_PORT
= 5647;
/**
* Maximale Anzahl der Spieler, die sich auf dem Server einwaehlen
* koennen
*/
private static final int
MAX_NUMBER_PLAYERS = 30;
/**
* ConnectionListener, der auf Verbindungsanfragen wartet
*/
private ConnectionListener connectionListener;
/**
* Anzahl der Spieler, die auf dem Server eingewaehlt sind
*/
private int
numberPlayers;
/**
* Schnittstelle zu einem Spieler
*/
private PlayerLinker []
playerLinker;
/**
* Initialisiert den GameServer.
*
* @param portNumber Portnummer, unter der der Server erreichbar sein
*
soll
*/
public GameServer (int portNumber)
{
numberPlayers
= 0;
playerLinker
= new PlayerLinker [MAX_NUMBER_PLAYERS];
connectionListener = new ConnectionListener (this, portNumber);
if (connectionListener.isListening ())
{
new CommandInput ();
print ("");
};
}
/**
* Fuegt einen Spieler zu der vorhandenen Liste der Spieler hinzu.
*
* @param socket Socket, ueber den Daten zum und vom Spieler gesendet
*
und empfangen werden koennen
* @return true, wenn der Spieler erfolgreich hinzu gefuegt wurde,
*
false, wenn schon die maximale Anzahl an Spielern
*
eingewaehlt ist
*/
public synchronized boolean addPlayerToList (Socket socket)
{
if (numberPlayers < MAX_NUMBER_PLAYERS)
{
playerLinker [numberPlayers++] = new PlayerLinker (socket, this);
Seite 162 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
return true;
}
else
{
print ("\nMaximum player count reached!");
return false;
}
}
/**
* Einstiegspunkt des GameServers. Prueft ob eine Portnummer als
* Argument ueber geben wurde. Wenn ja, wird diese genommen, wenn nein
* wir der vorgegebene Port benutzt
*
* @param args Argumente, die dem Programm ueber geben werden
*/
public static void main (String args [])
{
if (args.length == 0)
{
new GameServer (DEFAULT_PORT);
}
else if (args.length == 1)
{
try
{
new GameServer(Integer.parseInt (args[0]));
} catch (NumberFormatException e)
{
System.err.println ("Port must be non-negative number!");
};
}
else
{
System.err.println ("Usage: GameServer [port]");
};
}
/**
* Gibt ein Zeichenkette aus.
*
* @param output Zeichenkette, die ausgegeben werden soll
*/
public void print (String output)
{
System.out.print (output + "\n> ");
}
/**
* Entfernt den Spieler von der Liste der verfuegbaren Spieler.
*
* @param linker Schnittstelle des Spielers, die nicht mehr benoetigt
*
wird
*/
public synchronized void removePlayerFromList (PlayerLinker linker)
{
int
indexToStop
= MAX_NUMBER_PLAYERS;
boolean playerRemoved = false;
/* Fuehrt dazu, dass der Spieler, der auf dem Server eingewaehl ist,
* benachrichtigt wird, dass der Server beendet wird
Seite 163 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
linker.stopPlayer ();
for (int i = 0; i < numberPlayers; i++)
{
if (playerLinker [i] == linker)
{
indexToStop
= i;
playerRemoved = true;
};
};
for (int i = indexToStop; i < numberPlayers - 1; i++)
{
playerLinker [i] = playerLinker [i + 1];
};
if (playerRemoved)
{
numberPlayers--;
};
}
/**
* Liefert eine Zeichenkette, die aus allen Spielernamen besteht, die
* eingewaehlt sind und beim Aufruf der Funktion nicht gerade selber
* spielen ausser dem Spieler linker
*
* @param linker Schnittstelle des Spielers, dessen Name nicht in
*
der Liste erscheinen soll weil dieser die Anfrage gestellt
*
hat
* @return Zeichenkette mit allen verfuegbaren Spielern auf dem Server
*/
public synchronized String getAvailablePlayers (PlayerLinker linker)
{
String playersList = "";
for (int i = 0; i < numberPlayers; i++)
{
if ((playerLinker [i].getName () != null) &&
(playerLinker [i] != linker) &&
playerLinker [i].isAvailable ())
{
playersList += playerLinker [i].getName () + " ";
};
};
return playersList.trim ();
}
/**
* Liefert die Schnittstelle zum Spieler mit dem Namen name.
*
* @param name Name des Spielers, der gesucht werden soll
* @return Referenz auf den PlayerLinker des Spielers mit dem Namen
*
name falls vorhanden, ansonsten null
*/
public synchronized PlayerLinker getPlayer (String name)
{
for (int i = 0; i < numberPlayers; i++)
{
Seite 164 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
if (name.equals (playerLinker [i].getName ()))
{
return playerLinker [i];
};
};
return null;
}
/**
* Entfernt alle Spieler von der Serverliste. Wird aufgerufen, wenn
* der Server per Kommandozeile beendet wird.
*/
private void terminateAllPlayers ()
{
for (int i = 0; i < numberPlayers; i++)
{
playerLinker [i].stopPlayer ();
};
numberPlayers = 0;
}
/**
* Liefert eine Zeichenkette, die aus allen vefuegbaren Spielernamen
* auf dem Server besteht.
*
* @return Zeichenkette aus allen Spieler, sowohl von denen die gerade
*
spielen oder nicht spielen
*/
private synchronized String getPlayers ()
{
String playersList = "";
for (int i = 0; i < numberPlayers; i++)
{
if (playerLinker [i].getName () != null)
{
playersList += playerLinker [i].getName () + "
};
};
";
if (playersList.equals (""))
{
return "No players online!";
};
return playersList;
}
/**
* Innere Klasse zur Auswertung von Befehlen, die in der
* Kommandozeile eingebeben werden. Die drei Befehle, die zur
* Verfuegung stehen sind quit zum Beenden des Servers, show players
* zum Ausgeben einer Liste von allen verfuegbaren Spielern auf dem
* Server sowie ? zum Ausgeben aller bekannter Befehle an den Server
*/
private class CommandInput implements Runnable
{
/**
* Befehl, der auf der Kommandozeile eingebeben wird
Seite 165 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
*/
private String
command;
/**
* Dient zum Lesen des Eingabestroms
*/
private BufferedReader in;
/**
* Gibt an, ob Befehle ausgewertet werden sollen, wenn diese in der
* Kommandozeile eingegeben werden.
*/
private boolean
isStopped;
/**
* Initialisiert den Kommandoauswerter.
*/
CommandInput ()
{
in
= null;
command
= null;
isStopped = false;
in
= new BufferedReader (
new InputStreamReader (System.in));
(new Thread (this)).start ();
}
/**
* Liest solange Befehle von der Kommandozeile und wertet diese aus
* bis die Variable isStopped wahr ist.
*/
public void run ()
{
while (!isStopped)
{
try
{
command = in.readLine ();
if (command.equals ("quit"))
{
isStopped = true;
}
else if (command.equals ("?"))
{
print ("Available commands: \"?\", \"quit\" and " +
"\"show players\"");
}
else if (command.equals ("show players"))
{
print (getPlayers ());
}
else
{
print ("Available commands: \"?\", \"quit\" and " +
"\"show players\"");
};
} catch (IOException e)
{
e.printStackTrace ();
};
}
Seite 166 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
connectionListener.stopListening ();
terminateAllPlayers ();
}
/**
* Fuehrt dazu, dass keine Kommandoeingaben mehr ausgewertet werden.
*/
public void stop ()
{
isStopped = true;
}
}
}
ConnectionListener.java
package server;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Wartet auf Verbindungsanfragen von Spielern und leitet den Socket
* einer eingehenden Verbindung an den GameServer weiter.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;ConnectionListener.java<br>
* <b>Date:</b>10.11.2006<br>
*
* @author Mehmet Akin
* @version 1.0
*/
class ConnectionListener implements Runnable
{
/**
* GameServer, der die Verbindungen steuert
*/
private GameServer
gameServer;
/**
* Gibt an, ob auf dem server socket weiterhin gehorcht werden soll
* oder nicht
*/
private boolean
isStoppedListening;
/**
* Socket, auf dem auf Verbindungsanfragen gehorcht wird
*/
private ServerSocket
serverSocket;
/**
* Initialisiert das ConnectionListener-Objekt
*
* @param gameServer GameServer, an den die eingegangen Verbindungen
*
weiter geleitet werden
* @param portNumber Portnummer, auf dem der ServerSocket geoeffnet
*
werden soll
*/
public ConnectionListener (GameServer gameServer, int portNumber)
{
this.gameServer
= gameServer;
isStoppedListening = false;
Seite 167 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
try
{
serverSocket = new ServerSocket (portNumber);
System.out.println (
"GameServer started with Server-ip address: " +
InetAddress.getLocalHost ().getHostAddress () + " on port " +
portNumber);
/* Um ein Blockieren des Servers zu verhindern, wird auf
* Verbindungsanfragen in einem separaten Thread gewartet
*/
new Thread (this).start ();
} catch (IOException e)
{
System.err.println ("Port is already in use!\n" +
"Please choose another port!");
isStoppedListening = true;
};
}
/**
* Nimmt solange Verbindungsanfragen an, solange die Variable
* isStoppedListening auf false gesetzt ist.
*/
public void run ()
{
while (!isStoppedListening)
{
try
{
Socket socket = serverSocket.accept ();
/* Wenn eine Verbindungsanfrage angekommen ist, wird dieses an
* den GameServer zur weiteren Bearbeitung weitergeleitet
*/
if (gameServer.addPlayerToList (socket))
{
}
else
{
socket.close ();
};
} catch (IOException e)
{
};
};
}
/**
* Fuehrt dazu, dass der ConnectionListener nicht mehr horcht
* auf dem server socket.
*/
public synchronized void stopListening ()
{
isStoppedListening = true;
try
{
serverSocket.close ();
} catch (IOException e)
Seite 168 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
{
e.printStackTrace ();
};
}
/**
* Gibt an, ob der ConnectionListener auf dem server socket horcht.
*
* @return true, wenn auf Verbindungsanfrage gewartet wird, ansonsten
*
false
*/
public synchronized boolean isListening ()
{
return !isStoppedListening;
}
}
PlayerLinker.java
package server;
import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.StringTokenizer;
/**
* Implementiert das Protokoll zur Client-Server Kommunikation auf der
* Serverseite.Stellt Funktionen bereit zum Empfangen und Senden
* von und zum Client. Pro Spieler, der sich auf dem Server einwaehlt,
* gibt es einen PlayerLinker. Ueber diesen ist es moeglich, dass sich
* zwei Spieler zu einem Netzwerkspiel verabreden koennen.<br><br>
*
* <b>File:</b>&nbsp;&nbsp;PlayerLinker.java<br>
* <b>Date:</b>10.11.2006<br>
* <b>History:</b><br>
*
25.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Protokollelemente zum Beenden eines Spiels hinzu gefuegt<br>
*
12.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
PlayerLinker kann mit PlayerConnector auf der Client-Seite
*
kommunizieren<br>
*
11.11.06&nbsp;&nbsp;Mehmet Akin&nbsp;&nbsp;
*
Funktionen zum Einladen eines Spielers und zum Beenden eines
*
Spiels hinzu gefuegt<br>
*
* @author Mehmet Akin
* @version 1.3
*/
class PlayerLinker implements Runnable
{
/**
* Zeit, die vergehen darf bis ein eingeladener Spieler eine Antwort
* auf die Einladung zum Gegenspieler senden muss
*/
private static final int RESPONSE_TIMEOUT = 20000;
/**
* Dient dazu, das Timeoutverhalten des Sockets zurueck zu stellen,
* so dass keine Zeitbeschraenkungen beim Empfangen von Daten
* vorliegen
*/
private static final int NO_TIMEOUT
= 0;
Seite 169 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
/**
* Nachricht, die anzeigt, dass der gegnerische Spieler waehrend
* eines Netzwerkspiels das Spiel fruehzeitig beendet hat
*/
private static String
PLAYER_QUIT
= "PQ";
/**
* Anfrage des Clients, die Verbindung zum Server zu schliessen
*/
private static String
LOGOUT_REQ
= "RL";
/**
* Bestaetigung des Server auf eine Anfrage fuer den Verbindungsabbau
*/
private static String
LOGOUT_ACK
= "AL";
/**
* Nachricht, die anzeigt, dass der Server beendet wurde, waehrend
* noch Spieler eingewaehlt waren
*/
private static String
SERVER_QUIT
= "SQ";
/**
* Anfrage des Clients, sich beim Server einzuwaehleb
*/
private static String
LOGIN_REQ
= "LI";
/**
* Ablehnung einer Verbindungsanfrage an den Server, wenn der
* Benutzername schon vergeben ist
*/
private static String
LOGIN_REJ
= "LJ";
/**
* Bestaetigung des Servers zu einer Verbindungsanfrage des Clients
*/
private static String
LOGIN_ACK
= "LA";
/**
* Spielanfrage an einen verfuegbaren Spieler auf dem Server
*/
private static String
GAME_REQ
= "RE";
/**
* Ablehnung der Spieleinladung durch den gegnerischen Spieler
*/
private static String
GAME_REJ
= "RJ";
/**
* Annahme der Spieleinladung durch den eingeladen Spieler
*/
private static String
GAME_ACK
= "RA";
/**
* Timout, der dann vom Server an beide Spieler gesendet wird, wenn
* der eingeladene Spieler innerhalb von 20 Sekunden auf die
* Einladungsanfrage nicht antwortet
*/
private static String
TIMEOUT
= "TO";
/**
* Zentraler GameServer, der alle Verbindungen zu den eingewaehlten
* Spielern verwaltet
*/
private GameServer
gameServer;
/**
* Gibt an, ob der Spieler in dem Zeitpunkt der Anfrage zur
* verfuegung steht oder gerade in einem Spiel beteiligt ist
*/
private boolean
isAvailable;
/**
* Gibt an, oder der Spieler eien Einladung zu einem Spiel gesendet
Seite 170 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* hat
*/
private boolean
isGameRequester;
/**
* Gibt an, ob der PlayerLinker auf einkommende Daten wartet oder ob
* noch Daten versendet werden
*/
private boolean
isStopped;
/**
* Gibt den Namen des Spielers an, mit dieser auf dem Server
* eingewaehlt ist
*/
private String
name;
/**
* Gibt den Gegner des Spielers an, wenn der Spieler gegen diesen
* Spielt, ansonsten null
*/
private PlayerLinker
opponent;
/**
* Socket, uebe den Daten empfangen und gesendet werden
*/
private Socket
playerSocket;
/**
* Lesestrom zum Empfangen von Daten vom Client
*/
private BufferedReader
receiver;
/**
* Ausgabestrom zum Senden von Daten zum Client
*/
private PrintStream
sender;
/**
* Initialisiert den PlayeLinker.
*
* @param playerSocket Socket, von dem Daten empfangen werden oder
*
ueber den Daten gesendet werden koennen
* @param gameServer Server, der alle Verbindungen verwaltet
*/
public PlayerLinker (Socket playerSocket, GameServer gameServer)
{
this.playerSocket = playerSocket;
this.gameServer
= gameServer;
isAvailable
= true;
isGameRequester
= false;
name
= null;
try
{
receiver = new BufferedReader (new InputStreamReader (
playerSocket.getInputStream ()));
sender
= new PrintStream (playerSocket.getOutputStream (),
true);
/* Liest und empfaengt Daten in einem separaten Thread, um ein
* Blokieren des Servers zu verhindern
*/
(new Thread (this)).start ();
} catch (IOException e)
{
e.printStackTrace ();
};
Seite 171 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
}
/**
* Setzt alle Variablen auf Anfangswerte.
*/
public synchronized void resetState ()
{
resetTimeOut ();
isAvailable
= true;
isGameRequester = false;
opponent
= null;
}
/**
* Empfaengt solange Daten vom Socket oder kann solange Daten an den
* Client senden solange der Linker laueft.
*/
public void run ()
{
String received = null;
while (!isStopped)
{
received = getMessage ();
/* Wenn der Clientspieler abgestuerzt ist, gibt getMessage null
* zurueck
*/
if (received == null)
{
/* Wenn der Spieler, der abgestuerzt ist, in einem Spiel
* beteiligt war, so muss sein Gegner davon informiert werden,
* dass er nicht mehr eingewaehlt ist
*/
if (opponent != null)
{
opponent.sendMessage (PLAYER_QUIT);
};
gameServer.removePlayerFromList (this);
isStopped = true;
}
else
{
handleMessage (received);
};
};
if (received != null)
{
sendMessage (SERVER_QUIT);
};
closeConnection ();
}
/**
* Sendet Daten zum Client. Die Daten werden Zeichen fuer Zeichen als
* int-Werte gesendet.
*
Seite 172 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
* @param message Nachricht, die gesendet werden soll
*/
public synchronized void sendMessage (String message)
{
try
{
int length = message.length ();
for (byte i = 0; i < length; i++)
{
sender.write ((int) message.charAt (i));
};
sender.write ((int) '\n');
sender.flush ();
} catch (Exception e)
{
e.printStackTrace ();
};
}
/**
* Fuehrt dazu, dass der Linker gestoppt wird und keine weiteren Daten
* empfangen und senden kann.
*/
public synchronized void stopPlayer ()
{
isStopped = true;
sendMessage (SERVER_QUIT);
closeConnection ();
}
/**
* Liefert den Namen des Spielers zurueck.
*
* @return Name, mit der Spieler eingeloggt ist.
*/
public synchronized String getName ()
{
return name;
}
/**
* Gibt an, ob der Spieler verfuegbar ist und nicht gerade in einem
* Spiel beteiligt ist.
*
* @return true, wenn der Spieler verfuegbar ist, ansonsten false
*/
public synchronized boolean isAvailable ()
{
return isAvailable;
}
/**
* Fuehrt dazu, dass der Spieler verfuegbar ist oder nicht.
*
* @param isAvailable true, wenn der Spieler verfuegbar sein soll,
*
ansonsten false
*/
public synchronized void setIsAvailable (boolean isAvailable)
{
Seite 173 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
this.isAvailable = isAvailable;
}
/**
* Setzt den Gegner des Spielers, gegen den das Netzwerkspiel
* gestartet werden soll.
*
* @param opponent Spieler, gegen den gespielt werden soll
*/
public synchronized void setOpponent (PlayerLinker opponent)
{
this.opponent = opponent;
}
/**
* Schliesst alle Verbindungen, die geoeffnet wurden. Das sind die
* Ein-Ausgabestroeme sowie der Socket zum Empfangen und Senden von
* Daten an den Client.
*/
private void closeConnection ()
{
try
{
receiver.close ();
sender.close ();
playerSocket.close ();
} catch (IOException e)
{
e.printStackTrace ();
};
}
/**
* Wertet die Nachricht aus, die auf dem Socket empfangen wurde.
*
* @param message
*/
private void handleMessage (String message)
{
StringTokenizer parser = new StringTokenizer (message);
String
token = parser.nextToken ();
if (token.equals (LOGIN_REQ))
{
/* Das naechste Token muss der Benutzername sein, mit dem sich
* der Spieler einwaehlen moechte
*/
String loginName = parser.nextToken ();
/* Pruefe, ob der gewuenschte Name schon vergeben ist
if (gameServer.getPlayer (loginName) == null)
{
name
= loginName;
isAvailable = true;
*/
sendMessage (LOGIN_ACK + " " +
gameServer.getAvailablePlayers (this));
}
else
{
sendMessage (LOGIN_REJ);
Seite 174 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
};
}
else if (token.equals (GAME_REQ))
{
isGameRequester = true;
/* Solange dieser Spieler auf die Antwort auf eine Einladung zu
* einem Spiel wartet oder in einem Spiel beteiligt ist, wird
* dieser nicht auf der Liste der verfuegbaren Spieler angezeigt,
* wenn sich neue Spieler auf dem Server einwaehlen
*/
isAvailable
= false;
PlayerLinker tempOpponent = gameServer.getPlayer (
parser.nextToken ());
/* Wenn der Spieler, an den die Einladung gesendet werden soll,
* in der Zeit, in der die Einladung geschickt wird, schon eine
* andere Einladung von einem anderen Spieler erhalten hat oder
* ein Spiel gestartet hat, so erhaelt der einladende Spieler
* eine Ablehnung
*/
if (tempOpponent == null)
{
opponent = null;
sendMessage (GAME_REJ);
}
else if (tempOpponent.isAvailable ())
{
tempOpponent.setIsAvailable (false);
tempOpponent.setOpponent (this);
opponent = tempOpponent;
tempOpponent.sendMessage (GAME_REQ + " " + name);
setTimeOut ();
}
else
{
opponent = null;
sendMessage (GAME_REJ);
};
}
else if (token.equals (GAME_ACK))
{
if (!isGameRequester)
{
opponent.sendMessage (GAME_ACK);
};
resetTimeOut ();
}
else if (token.equals (GAME_REJ))
{
/* Wenn eine Ablehnung auf eine Einladung vom Gegenspieler
* gesendet wurde, muss der Status des einladenden Spielers wieder
* zurueck gesetzt werden
*/
isAvailable
= true;
Seite 175 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
isGameRequester = false;
resetTimeOut ();
if ((opponent != null) && (!opponent.isAvailable ()))
{
opponent.sendMessage (GAME_REJ);
};
opponent = null;
}
/* Wenn der eingeladene Spieler innerhalb des Timeouts nicht
* antwortet, so wird eine TIMEOUT Signal sowohl an den einladenden
* als auch an den eingeladenen Spieler geschickt. Das fuehrt dazu,
* dass beim einladenden Spieler wieder die Liste mit den
* verfuegbaren Spielern angezeigt wird und beim Eingeladenen das
* Einladungsfenster erlischt und auch die Spielerliste angezeigt
* wird
*/
else if (token.equals (TIMEOUT))
{
isAvailable
= true;
isGameRequester = false;
opponent.resetState ();
sendMessage (TIMEOUT);
opponent.sendMessage (TIMEOUT);
resetTimeOut ();
}
else if (token.equals (LOGOUT_REQ))
{
if (opponent != null)
{
if (!opponent.isAvailable ())
{
/* Wenn sich ein Spieler waehrend einer Einladung zu einem
* Spiel oder waehrend eines angefangenen Spiels vom Server
* auswaehlt, so muss dessen Gegner informiert werden, dass
* dieser nicht mehr erreichbar ist
*/
opponent.sendMessage (PLAYER_QUIT);
gameServer.removePlayerFromList (opponent);
};
};
sendMessage (LOGOUT_ACK);
gameServer.removePlayerFromList (this);
resetState ();
}
else
{
opponent.sendMessage (message);
};
}
/**
* Setzt das Timeout des Sockets nachdem ein Spieler auf eine
* Einladung geantwortet hat, oder das Timeout abgelaufen ist, weil
* der Spieler nicht geantwortet hat.
*/
Seite 176 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
private void resetTimeOut ()
{
try
{
playerSocket.setSoTimeout (NO_TIMEOUT);
} catch (SocketException e)
{
e.printStackTrace ();
};
}
/**
* Empfaengt eine Nachricht vom Client.
*
* @return Nachricht, die empfangen wurde als Zeichenkette
*/
private String getMessage ()
{
int
c;
StringBuffer buffer = new StringBuffer ();
try
{
while ((c = receiver.read ()) != '\n')
{
buffer.append ((char) c);
};
return buffer.toString ();
} catch (SocketTimeoutException e)
{
resetTimeOut ();
e.printStackTrace ();
return TIMEOUT;
} catch (IOException e)
{
e.printStackTrace ();
return null;
};
}
/**
* Setzt das Timout des Sockets sobald eine Spieleinladung von dem
* Spieler an den gewuenschten Gegenspieler gesendet wurde.
*/
private void setTimeOut ()
{
try
{
/* Der eingeladene Spieler muss nun innerhalb der angegeben Zeit
* die Antwort auf die Einladung senden
*/
playerSocket.setSoTimeout (RESPONSE_TIMEOUT);
} catch (SocketException e)
{
e.printStackTrace ();
};
}
}
Seite 177 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Literaturverzeichnis
[1]
Mobile Java 3D JSR-184-Spezifikation: http://jcp.org/en/jsr/detail?id=184
[2]
Einfuehrung in J2ME-Programmierung: http://java.sun.com/javame/technology/index.jsp
[3]
Code-Beispiele fuer Mobile Java 3D-Programmierung:
http://developer.sonyericsson.com/site/global/techsupport/tipstrickscode/mobilejava3d/p_
mobilejava3d_tips_new.jsp
[4]
Einfuehrungskapitel Mobile Java 3D:
http://www.awprofessional.com/articles/article.asp?p=381391&rl=1
[5]
M3G-Online-Probekapitel aus „Killer Game Programming“, Andrew Davison, O’Reilly,
2005: http://fivedots.coe.psu.ac.th/~ad/jg/
[6]
Grundlagen Mobile Java 3D:
http://developers.sun.com/techtopics/mobility/apis/articles/3dgraphics/
[7]
3D-Grafiken fuer Mobile Endgeraete, M3G, Immediate Mode:
http://www-128.ibm.com/developerworks/wireless/library/wi-mobile1/
[8]
3D-Graphiken fuer Mobile Endgeraete, M3G, Immediate Mode:
http://www-128.ibm.com/developerworks/wireless/library/wi-mobile2/
[9]
Entwicklung Mobiler 3D-Grafiken fuer J2ME:
http://www.informit.com/articles/article.asp?p=379941&rl=1
[10]
Blender Dokumentation: http://www.blender.org/documentation/htmlI/book1.html
[11]
Blender: Noob to Pro: http://en.wikibooks.org/wiki/Blender_3D:_Noob_to_Pro
[12]
J2ME-Netwerkprogrammierung:
http://www.wirelessdevnet.com/channels/java/features/j2me_http.phtml
[13]
J2ME-Tutorial: http://today.java.net/pub/a/today/2005/02/09/j2me1.html?page=1
[14]
Alpha-Beta-Algorithmus: http://de.wikipedia.org/wiki/Alpha-Beta-Suche
[15]
Alpha-Beta-Algorithmus: http://www.seanet.com/~brucemo/topics/alphabeta.htm
[16]
Spielalgorithmen:
http://www.iicm.tugraz.at/Teaching/theses/2000/_idb9e_/greif/node7.html
[17]
Alpha-Beta-Pruning: http://www.netlib.org/utk/lsi/pcwLSI/text/node351.html
[18]
MiniMax-Algorithmus: http://www.cs.mcgill.ca/~cs251/OldCourses/1997/topic11/
[19]
J2ME-Netzwerkprogrammierung:
http://developers.sun.com/techtopics/mobility/midp/articles/midp2network/
Der Inhalt dieser Webseiten wird auch als Offline-Version auf der CD mit geliefert.
Seite 178 von 179
Entwicklung eines 3D-Handyspiels im Fach Systemprogrammierung 2006/2007
Abbildungsverzeichnis
Abbildung 2-1: Brettstellungen bei Spielanfang.............................................................................6
Abbildung 2-2: Steuerung des Spiels ..............................................................................................7
Abbildung 2-3: Startmenue und Untermenues des Spiels...............................................................7
Abbildung 2-4: Fenster zum Starten eines Netzwekspiels..............................................................8
Abbildung 2-5: 3D-Spielelandschaft...............................................................................................9
Abbildung 3-1: Klassendiagramm des Spiels ...............................................................................10
Abbildung 3-2: Bewertungsmatrix des Spielbretts .......................................................................13
Abbildung 3-3: Alpha-Beta Algorithmus bei einer Tiefe von 3 ...................................................15
Abbildung 3-4: Textobjekt modelliert mit Blender3D..................................................................17
Abbildung 3-5: Modelle, modelliert mit Blender3D.....................................................................21
Abbildung 3-6: Aufbau eines Mesh-Objektes im Koordinatenursprung ......................................22
Abbildung 3-7: Zuweisen einer Textur zu einer Kachel ...............................................................23
Abbildung 3-8: Kamera mit Perspektivprojektion ........................................................................24
Abbildung 3-9: Szenegraph der Spielelandschaft .........................................................................24
Abbildung 3-10: Zuganimation mit fliegenden Schafen...............................................................26
Abbildung 3-11: Zuganimation mit explodierenden Schafen .......................................................26
Abbildung 3-12: Erfolgreiches Einwaehlen auf dem Server ........................................................28
Abbildung 3-13: Nicht erfolgreiches Einwaehlen auf dem Server ...............................................29
Abbildung 3-14: Einladung zu einem Spiel ..................................................................................29
Abbildung 3-15: Timeout bei der Spieleinladung.........................................................................30
Abbildung 3-16: Spielende bei Serverstop....................................................................................30
Abbildung 3-17: Spielende beim Auswaehlen eines Spielers.......................................................31
Abbildung A-1: Java Platform ......................................................................................................35
Abbildung A-2: Szenegraph nach JSR-184-Spezifikation...........................................................36
Abbildung A-3: Objekt-ID-Baum des Szenegraphen ...................................................................39
Tabellenverzeichnis
Tabelle A-1: Zeitaufwand fuer den Entwicklungsprozess ............................................................41
Seite 179 von 179