Teamprojekt Hochschule Trier 2015-2016 Java-basiertes Framework zur Erstellung eines interaktiven Spielbretts für Multitouch-Geräte von Kore Kaluzynski, Cenk Saatci, Christian Colbach
Zur Erstellung einer ausführbaren Datei muss das src Verzeichnis kompiliert werden.
Die Hauptklasse der Anwendung ist die Klasse thegamebrett.gui.GUIApplication. Dieser muss zur Ausführung ein Parameter übergeben werden, welcher inklusive /
den absoluten Pfad des Assetsordners angibt. Dieser Ordner ist in scr/assetsfolder zu finden und kann in ein beliebiges Verzeichnis im Dateisystem verschoben werden:
$ java thegamebrett.gui.GUIApplication /Users/grompa/Documents/gamebrett/classes/assetsfolder/
 Prinzipieller Aufbau des Projektes
Mit einer Trennung zwischen Spielelogik und I/O
war es möglich, das Framework stark MVC-orientiert umzusetzen. Dies hat den Vorteil dass die einzelnen Spiele komplett austauschbar sind, ohne dass dafür eine Zeile Code im Rest des Systems geändert werden muss.
Der Manager stellt sozusagen den "Dreh und Angelpunkt" der Anwendung dar. Über ihn laufen alle Kommunikationen und er dient als Verbindungsglied aller Komponenten (direkt oder indirekt).
Die Grafische Benutzeroberfläche welche auf dem Gerät selbst angezeigt wird setzt auf JavaFX. Der Aufbau wechselt lediglich zwischen zwei Scenes. Der MenueView
und der GameView
, welche den Kern der Benutzeroberfläche darstellen.
Die MenueView
stellt die erste Scene dar. Diese wird beim Start der Anwendung gesetzt und dient zur Darstellung des Menüs, sowie zur Auswahl der vorhandenen Spiele. Zur direkten Funktion wird sie jedoch nicht benötigt (auch wenn sie diese ergänzt) da die Funktionen nicht auf die direkte Eingabe am Gerät angewiesen ist (Mehr dazu unter Steuerungskonzept).
Die GameView
stellt die zweite Scene dar. Sie ist generisch und stellt immer das aktuelle Spiel dar. Die einzelnen Spiele werden in ihr geladen und auch animiert (Bewegung von Figuren und Feldern).
Das Webinterface verkörpert in unserem Aufbau die Grundlage aller Kommunikationen mit dem Spieler. Jeder Spieler muss sich über sein Smartphone (bzw. einem anderen internetfähigen Gerät) an den Server anmelden, welcher als Verbingung zur Interaktion mit dem System und damit auch mit dem Spiel dient.
Die Spielelogik und die dazugehörigen Klassen des Models stellen das Spiel im Eigentlichen dar. Sie ist komplett austauschbar und kann selbst zur Laufzeit beliebig oft ausgetauscht werden. Dies ermöglicht es den Spielern sich gemeinsam für ein Spiel zu entscheiden und dieses anschliessend zu spielen.
Die am System angemeldeten User werden vom Usermanager verwaltet. Jeder Spieler erhält einen wählbaren Sitzplatz und einen dazugehörigen wählbaren Character.
Die Komunikation zwischen den einzelnen Modulen (Model, Grafik, Webinterface, Spielelogik) findet über einen selbst implementiertes System aus Actions statt. Grundlage hierfür stellen die Interfaces ActionRequest
und ActionResponse
von welchen es eine bestimmte Menge von Implementierungen gibt, welche durch das gesamte System durchgereicht werden. Hierbei leitet der Manager kontinuierlich Requests von der Spielelogik an die gewünschte Systemkomponente weiter (z.B. Netzwerk oder UI). Diese Systemkomponenten generieren anschliessend die passende Responses welche danach wieder an den Manager übergeben werden. Hirbei ist zu beachten dass die Spielelogik immer nur beim "Anstoss" durch den Manager aktiv wird und ihre nächste Request generiert. Ein solcher Anstoss geschieht nur wenn eine Response an die Spielelogik zurück geht. Um diesen Aufbau zu verstehen sind folgende Methoden relevant:
Gamelogik
):
```java
public ActionRequest[] next(ActionResponse as);
````
In dem Manager (Manager
, MobileManager
, TimeManager
, NetworkManager
):
```java
public void react(ActionResponse response);
````
Das Brett ist so konzipiert dass eine Steuerung mittels Maus oder Touchscreen nicht nötig ist. Es genügt wenn das Spielbrett mit dem WLAN verbunden ist, beziehungsweise einen eingebauten Access Point enthält. Das Menü und die Spiele werden über die externen Geräte, welche mit dem Brett verbunden sind, gesteuert.
Zum Laden von Assets wird die Klasse AssetLoader
verwendet, welche das Laden von Inhalten und lokalisierten Textdokumenten vereinfacht (Mehr dazu im nächsten Abschnitt Lokalisierung).
Für die Lokalisierung unserer Anwendung verwenden wir das Java-eigene Lokalisierungsframework java.util.ResourceBundle
. Dieses erlaubt es sprachunabhängige Strings zu definieren und diese anschliessend in verschiedene Sprachen zu übersetzen.
Die Einstellung der Sprache kann in der UI im Menu vorgenommen werden. Hierzu klickt man auf den Button [Optionen] und stellt diese anschließend unter den Spracheinstellungen ein.
Bei der Verwendung unterscheiden wir im Folgenden zwischen der Lokalisierung des Java-Codes und der Lokalisierung der HTML-Seiten. Hierzu ist außerdem zu erwähnen, dass alle Strings welche verwendet werden sollen, in den entsprechenden properties
-Dateien angegeben sein müssen. Für Englisch beispielsweise in der Datei languages_en.properties
, für Deutsch languages_de.properties
.
In Java können Strings durch die direkte Verwendung des ResourceBundle
-Objektes lokalisiert werden. Dieses ist statisch und befindet sich im Manager. Es kann durch Manager.rb
angesprochen werden. ResourceBundle
besitzt eine Funktion getString(String)
, diese Methode gibt bei Angabe eines Keys einen entsprechend lokalisierten String zurück.
```java System.out.println(Manager.rb.getString("StartGame")); ``` Gibt als Ausgabe
Spiel beitreten
zurück wenn die Sprache auf Deutsch gestellt ist und Join game
wenn die Sprache auf Englisch gestellt ist.
Zur Lokalisierung der HTML-Seiten verwenden wir ein eigenes System welches durch unseren AssetsLoader realisiert wird. Dieser durchsucht die Dateien On-the-fly nach dem Vorkommen dieser Symbole ##
und ersetzt den zwischen diesen Symbolen vorkommenden Text durch den passenden Eintrag im ResourceBundle
.
##JoinGame##
Wird beim Laden automatisch ersetzt durch: Spiel beitreten
wenn die Sprache auf Deutsch gestellt ist und durch Join game
wenn die Sprache auf Englisch gestellt ist.
Bei der Verwendung dieser Art der Lokalisierung ist darauf zu achten, dass auf diese Weise __nur__ die HTML-Seiten welche durch unseren AssetsLoader geladen werden (also als Datei von der Festplatte) lokalisiert werden! Sämtliche anderen Strings welche dem Server per Java-Code übergeben werden __müssen__ wie im vorherigen Punkt beschrieben lokalisiert werden.
Da unser Framework sehr generisch gehalten ist können neue Spiele mit wenig Aufwand erstellt werden.
### Aufbau  Prinzipieller Aufbau des Models
Im Paket thegamebrett.model
sind die Klassen zusammengefasst, auf welchen die Spiele basieren müssen damit das Framework mit ihnen arbeiten kann.
Klasse: thegamebrett.model.Model
Die Klasse Model ist die einzige Klasse über die der Manager auf das Spiel zugreift und unter welcher die weiteren Klassen des Spieles organisiert sind. Es ist nicht nötig (aber erlaubt) von ihr abzuleiten. Objekte dieser Klasse werden innerhalb der GameFactory
-Klasse erzeugt.
Abstrakte Klasse: thegamebrett.model.Player
Die Klasse Player ist die Klasse welche einen Spieler im Spiel repräsentiert. Sie ist abstrakt, obwohl sie keine abstrakten Methoden enthält und muss somit vererbt werden. Der Grund hierfür ist dass die Logik, welche dem Spieler die Figuren zuordnet, in dieser Klasse bestimmt werden soll. Die Zuordnung von Figuren kann entweder berechnet werden indem die Methode public Figure[] getFigures();
überschrieben wird oder indem die ArrayList ArrayList
figures
befüllt wird.
Abstrakte Klasse: thegamebrett.model.elements.Element
Diese Klasse dient als Vater aller Komponenten welche die UI zeichnen kann. Element
erweitert alle Klassen um eine Funktion public void registerChange()
diese Funktion muss zwingend aufgerufen werden wenn eine Eigenschaft der Klasse verändert wird welche für die UI interessant sein könnte. Wird diese Funktion __nicht__ aufgerufen, werden die vom Model abhängigen UI-Komponenten auch __nicht__ aktualisiert.
Bevor wir auf den Aufbau der einzelnen Elemente eingehen gibt es noch ein paar Dinge zu beachten, welche sich auf das verwendete Koordinatensystem beziehen. Der Bereich der Koordinaten reicht von 0 bis 1. Der Punkt der sich unten rechts befindet ist also (1, 1) und der Punkt welcher sich oben links befindet ist (0, 0). Hierbei zu beachten ist, dass dies nicht dem Punkt (0, 0) des Displays entsprechen muss. Mehr dazu im folgendem Abschnitt über die Klasse Board
.
Abstrakte Klasse: thegamebrett.model.elements.Board
Diese Klasse repräsentiert im Model das Spielbrett im eigentlichen Sinne, also die Unterlage auf der sich die Felder und die Figuren befinden. Die Klasse ist abstrakt und muss somit zwingend vererbt werden. Zusätzlich müssen folgende Methoden implementiert werden:
Soll die Anzahl der Felder zurückgegeben werden welche sich auf dem Brett befinden:
public abstract int getFieldLength();
Soll das Feld mit dem index i zurückgeben werden:
public abstract Field getField(int i);
Soll das Layout des Bretts zurückgeben werden (später mehr dazu):
public abstract Layout getLayout();
Soll die Ratio des Brettes zurückgeben werden. Das Verhältnis zwischen X und Y gibt das Verhältnis zwischen Breite und Höhe an. Ein Ratio 1 zu 1 entspricht einem Quadratischen Spielfeldes. Das Koordinatensystem ist von dieser Angabe abhängig:
public abstract float getRatioX();
public abstract float getRatioY();
Abstrakte Klasse: thegamebrett.model.elements.Field
Diese Klasse repräsentiert im Model ein Spielfeld. Die Klasse ist abstrakt und muss somit zwingend vererbt werden. Zusätzlich müssen folgende Methoden implementiert werden:
Soll die relative horizontale und vertikale Position zurück geliefert werden:
public abstract RelativePoint getRelativePosition();
Soll die relative Breite zurückgeben werden (Wert ∈ [0d, 1d]):
public abstract double getWidthRelative();
Soll die relative Höhe zurückgeben werden (Wert ∈ [0d, 1d]):
public abstract double getHeightRelative();
Soll das nächste Feld zurückgeben werden:
public abstract Field[] getNext();
Soll das vorhergehendes Feld zurückgeben werden:
public abstract Field[] getPrevious();
Soll das Layout des Feldes zurückgeben werden (später mehr dazu):
public abstract Layout getLayout();
Klasse Figure: thegamebrett.model.elements.Figure
Die Klasse Figure
ist die Klasse welche im Model eine Spielfigur darstellt. Sie hat einen Besitzer (Player
), ein Layout sowie eine relative Breite und Höhe (∈ [0d, 1d]). Die Klasse ist nicht abstrakt und muss somit nicht vererbt werden. Jedoch ist dies für viele Spiele anzuraten, da dies der Spielelogik helfen kann diesen zusätzliche Informationen zu übergeben.
Klasse Layout: thegamebrett.model.elements.Figure
Diese Klasse definiert das Aussehen von Elementen. Sie hat viele optionale Werte die gesetzt werden können um einem Element ein bestimmtes Aussehen zu verpassen. Hierfür kann Form, Bild, Text, Schriftgrösse, Textur, Hintergrundfarbe, Rand und Randgröße festgelegt werden. Das Grafikmodul (GUILoader
) arbeitet generisch und kann somit diese Attribute allen Elementen gleichermaßen verleihen.
Interface: thegamebrett.model.GameFactory
Die Klasse welche Gamefactory
implementiert ist die Klasse welche Spiele erstellen kann. Hierfür muss diese Klasse eine Reihe von Funktionen liefern welche von aussen Informationen über das Spiel bereitstellen und die Möglichkeit bieten ein Model des Spieles zu erzeugen. Folgende Methoden müssen implementiert werden:
Soll das Icon des Spieles zurückgeben werden:
public Image getGameIcon();
Soll der Namen des Spieles zurückgeben werden:
public String getGameName();
Soll die maximale bzw. minimale Anzahl an Spielern zurück geben werden:
public int getMaximumPlayers();
public int getMinimumPlayers();
Soll eine Instanz des Spieles erzeuget werden (Model
-Objekt):
public Model createGame(ArrayList<User> users) throws TooMuchPlayers, TooFewPlayers;
Abstrakte Klasse: thegamebrett.model.GameLogic
Diese Klasse ist die Klasse welche für die Logik im Spiel zuständig ist. In ihr wird der Ablauf des Spieles gesteuert. Sie ist abstrakt und muss somit zwingend vererbt werden. Außerdem müssen zwei Methoden implementiert werden:
Diese Methode soll die Startpositionen der Spieler bestimmen: ```java public abstract Field getNextStartPositionForPlayer(Player player); ``` Diese Methode ist die wichtigste Methode im Model. Sie entscheidet den Verlauf des Spieles. Sie bekommt als Eingabe immer eine Response und gibt als Rückgabewert eine Liste von Requests zurück: ```java public abstract ActionRequest[] next(ActionResponse as); ```Requests und Responses sind für den aktiven Verlauf des Spieles verantwortlich. Es geht nie eine Interaktion vom Spiel selbst aus, die Kommunikation erfolgt immer nach dem Ping-Pong-Model. Das Model ist somit nur aktiv wenn es angestoßen wird.
##### GUI Stößt die UI dazu an, sich zu aktualisieren: ```java thegamebrett.action.request.GUIUpdateRequest(int value, boolean animated, int delay) ``` Beispiel Aufruf: ```java new GUIUpdateRequest(GUIUpdateRequest.GUIUPDATE_FIELDS+GUIUpdateRequest.GUIUPDATE_FIGURES, true, 0) ```Verlangt eine Interaction von einem bestimmten Spieler:
thegamebrett.action.request.InteractionRequest(String titel, Object[] choices, Player player, boolean hidden, String acknowledgment, int delay, Object userData)
Bei dieser Anfrage wird ein Objekt vom Typ InteractionResponse
zurück gegeben.
Zeigt eine Nachricht für alle sichtbar auf dem Spielbrett an. Die Nachricht ist in Richtung des angegebenen Spielers gedreht:
thegamebrett.action.request.ScreenMessageRequest(String label, Player player)
Die Nachricht verschwindet erst wieder, wenn eine neue Nachricht geschickt oder falls eine RemoveScreenMessageRequest
zurück gegeben wird.
Spielt einen Sound ab. Ein Soundobjekt muss im wav
-Format vorliegen. Als Parameter muss ein Objekt vom Typ thegamebrett.model.mediaeffect.SoundEffect übergeben werden:
thegamebrett.action.request.PlaySoundRequest(SoundEffect sound)
Sound stoppt erst wenn das Soundobjekt zu Ende geht oder falls StopSoundsRequest
zurück gegeben wird. Falls im SoundEffect angegeben wird dass sich dieser für immer wiederholt stoppt dieser erst bei StopSoundsRequest
oder wenn das Spiel beendet wird.
Da das gesamte Framework darauf aufgebaut ist, dass die Spielelogik immer erst anläuft wenn eine Response für diese vorliegt, ist es in manchen Situationen sinnvoll einen Timer zu starten:
thegamebrett.action.request.TimerRequest(int millis)
Dieser Timer hat die Eigenschaft nach einer bestimmten Anzahl von Sekunden eine TimerResponse
zurück zu geben. Auf diese Art kann das Framework sich nach Ablauf einer bestimmten Zeit wieder selbst anstoßen (Auf diese Art und Weise sind somit auch Schleifen möglich wenn man immer ein TimerRequest
zurück gibt).
Spielstart und Spielende werden ebenfalls über Requests und Responses geregelt. Beim Starten des Spieles erhält die GameLogik eine StartPseudoResponse. Diese enthält keine Daten und dient nur dazu das Spiel anzustoßen.
Das Spiel ist erst dann beendet wenn die GameLogik eine GameEndRequest
zurückgibt.
Die GameCollection ist die Klasse, in die alle Spiele eingetragen werden müssen, damit sie vom Framework erkannt werden. Von jedem existierenden Spiel muss sich eine Instanz der entsprechenden GameLogik innerhalb der GameCollection befinden.
```java public static GameFactory[] gameFactorys = { new D_GameFactory(), new PSS_GameFactory() }; ```Als Beispiel-Implementierung haben wir 3 Spiele realisiert. Diese sind unter thegamebrett.game.MADN.
, thegamebrett.game.KFSS.
, thegamebrett.game.PSS.
sowie thegamebrett.game.dummy.
zu finden. Der Dummy dient hierbei als einfaches Beispiel einer minimalen Version eines Spieles und lässt sich gut als Grundlage für neue Spiele verwenden.
Zum Anlegen eigener Character muss die Datei assetsfolder/characters.csv
um eine weitere Zeile erweitert werden.
Rot;#FF3333;tod-red.png Blau;#0080FF;tod-blue.png Green;#00CC00;tod-green.png