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> 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> 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> 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> IntelligenceHard.java<br> * <b>Date:</b>08.11.2006<br> * <b>History:</b><br> * 09.11.06 Mehmet Akin * 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> 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> 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> 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> 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> Game.java<br> * <b>Date:</b>21.10.2006<br> * <b>History:</b><br> * 28.11.06 Mehmet Akin * 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 Mehmet Akin * 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 Mehmet Akin * 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> GameController.java<br> * <b>Date:</b>21.10.2006<br> * <b>History:</b><br> * 28.11.06 Mehmet Akin * Controller liefert nun den SoundPlayer an anlle Klassen, * die Toene abspielen muessen<br> * 25.11.06 Mehmet Akin * Spiel startet nun nicht direkt, muss stattdessen ueber das * 3D-Menue gestartet werden<br> * 24.11.06 Mehmet Akin * 3D-Menue integriert mit getMenue3DLoader<br> * 06.11.06 Mehmet Akin * getIdleListener-Funktion hinzugefuegt<br> * 05.11.06 Mehmet Akin * 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> 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> OpponentChooser.java<br> * <b>Date:</b>11.11.2006<br> * <b>History:</b><br> * 27.11.06 Mehmet Akin * Ein Spieler kann einen anderen zum Spiel einladen<br> * 26.11.06 Mehmet Akin * Formular hinzu gefuegt, mit dem sich ein Spieler auf dem * Server einwaehlen kann<br> * 25.11.06 Mehmet Akin * Netzwerkspiel kann gestartet werden<br> * 12.11.06 Mehmet Akin * 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> PlayerConnector.java<br> * <b>Date:</b>11.11.2006<br> * <b>History:</b><br> * 27.11.06 Mehmet Akin * Sieler kann nun eine Einladung ablehnen<br> * 26.11.06 Mehmet Akin * Funktion closeConnection hinzu gefuegt<br> * 13.11.06 Mehmet Akin * Mit dem Protokoll kann nun auch das serverseitige oder * spielerseitige Beenden des Spiels erkannt werden<br> * 12.11.06 Mehmet Akin * 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> Renderer.java<br> * <b>Date:</b>05.11.2006<br> * <b>History:</b><br> * 18.11.06 Mehmet Akin * Graphics und Graphics3D referenz werden nicht mehr direkt * ueber geben, sondern sind ueber das Argument board3D * zugreifbar<br> * 13.11.06 Mehmet Akin * 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> RendererBombSet.java<br> * <b>Date:</b>05.11.2006<br> * <b>History:</b><br> * 19.11.06 Mehmet Akin * 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> 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> 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> 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> 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 Mehmet Akin * 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 Mehmet Akin * 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 Mehmet Akin * Spielnachrichten werden bei Anfang und Ende des Spiels * angezeigt<br> * 24.11.06 Mehmet Akin * Baeume in Form von 3D-Sprites wurden um das Spielfeld * platziert<br> * 21.11.06 Mehmet Akin * 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 Mehmet Akin * Steine, die gedreht werden muessen, werden nun an die * Funktion set uebergeben<br> * 04.11.06 Mehmet Akin * 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> 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> 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> 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> Object3DLoader.java<br> * <b>Date:</b>03.11.2006<br> * <b>History:</b><br> * 22.11.06 Mehmet Akin * 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> Menu3D.java<br> * <b>Date:</b>21.10.2006<br> * <b>History:</b><br> * 24.11.06 Mehmet Akin * 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> Menu3DLoader.java<br> * <b>Date:</b>21.10.2006<br> * <b>History:</b><br> * 28.11.06 Mehmet Akin * Hintergrund-Bild wird nun auch geladen<br> * 24.11.06 Mehmet Akin * 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> MenuGameLevel.java<br> * <b>Date:</b>21.10.2006<br> * <b>History:</b><br> * 25.11.06 Mehmet Akin * 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> MenuGameStart.java<br> * <b>Date:</b>21.10.2006<br> * <b>History:</b><br> * 25.11.06 Mehmet Akin * 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> MenuGameType.java<br> * <b>Date:</b><br> * <b>History:</b>21.10.2006<br> * 25.11.06 Mehmet Akin * 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> 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> 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> PlayerHuman.java<br> * <b>Date:</b>22.10.2006<br> * <b>History:</b><br> * 06.11.06 Mehmet Akin * 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> 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> 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> Reversi.java<br> * <b>Date:</b>23.10.2006<br> * <b>History:</b><br> * 07.11.06 Mehmet Akin * 2D zu 3D Mapper Variable hinzu gefuegt<br> * 06.11.06 Mehmet Akin * 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> 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> 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> 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> PlayerLinker.java<br> * <b>Date:</b>10.11.2006<br> * <b>History:</b><br> * 25.11.06 Mehmet Akin * Protokollelemente zum Beenden eines Spiels hinzu gefuegt<br> * 12.11.06 Mehmet Akin * PlayerLinker kann mit PlayerConnector auf der Client-Seite * kommunizieren<br> * 11.11.06 Mehmet Akin * 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