Justin Diaz                                        5115108                                    24.07.2018

Projektdokumentation:

Rasende Roboter

1. Analyse der Multiplayer Game Konzepte:

Runden-Basiertes Multiplayer:

In einem Turn-Based (auf deutsch: rundenbasiertes) spiel, teilt man

unter mehreren Spielern einen einzigen Spiel Zustand, nur ein Spieler

hat  ausschließlich in dem moment an dem er dran ist, rechte darauf diesen Spiele Zustand zu modifizieren. Spieler wechseln sich Asynchron ab, die Reihenfolge dessen beruht sich lediglich auf den Regeln des Spiels.

Echtzeit Multiplayer:

Hier verbinden sich mehrere Spieler an einem Server an dem in Echtzeit Daten hoch- und heruntergeladen werden. Die geschehnisse des Spiel aktualisieren sich auch in Echtzeit (Unter der  bedingung das man eine gute Verbindung hat).

2. Wahl des Multiplayer Game Konzeptes:

Das Multiplayer Game Concept, welches sich am sinnvollsten erwiesen hat, ist das Turn-Based  Konzept. Grund hierfür: Da das originale Brettspiel, auf dem unsere App basiert, Turn-Based ist, gibt es keinen Grund zu “kreativ” zu werden bei der Wahl des Multiplayer Konzeptes.

Modell zu den möglichen Zuständen des Turn-Based Systems:

3. Wahl des Multiplayer APIs:

Wir haben uns für die Firebase API entschieden, weil es sich aufgrund der ausführlichen/verständlichen Dokumentation und der notwendigen Funktionen die wir für unser Projekt gebrauchen als beste Wahl anbietet. Zudem läuft die Datenbank in Echtzeit was sich für unsere temporäre Datenspeicherung am vorteilhaftesten Erweist.

Um genau zu sein, handelt es sich um eine Client-Side API. Eine Client-Side API bietet den Vorteil, dass der Client sich nicht mit den serverseitigen Anforderungen beschäftigen muss, wie z.B Server Erschaffung, Server Einstellung, Server Instandhaltung, Server Funktionen usw., wir müssen uns lediglich nur auf den clientseitigen Bereich konzentrieren.

Client-Side API Modell:

Als Client (im Bild: Java oder auch die Applikation) nutzt man hauptsächlich die Funktionen (im Bild: Firebase API) , die der Server (Im Bild: Firebase) anbietet, um die Applikation mit dem Server zu verbinden. Besteht eine Verbindung, kann man alles Mögliche mit den vorhandenen Daten anstellen. Man kann Daten in die Echtzeit-Datenbank hochladen, ablesen, verändern oder löschen (sofern sie überhaupt in der Datenbank existieren). Diese aufgelisteten Möglichkeiten, Daten zu manipulieren, werden durch die o.g. Funktionen des Firebase APIs ermöglicht. Beim schreiben des Codes muss man darauf achten, dass alle Methoden des Firebase APIs - die mit dem Hochladen/Herunterladen von Daten zu tun haben - asynchron basiert sind. Näheres dazu finden sie aber im nächsten Abschnitt.

4. Asynchrone Datenübertragung

Begriffserklärung: Asynchrone Datenübertragung

Die asynchrone Datenübertragung ist ein Übertragungsverfahren der Nachrichtentechnik, bei dem Zeichen asynchron, das heißt zu beliebigen Zeiten, übertragen werden. Die Übertragung ist also, im Gegensatz zur synchronen Datenübertragung, nicht an einem Taktsignal ausgerichtet.”

-Wikipedia

Beispiel im Kontext unseres Spieles: 

Allgemein gilt die Regel, dass jedes mal wenn ein Button gedrückt wird und es zum Hochladen oder Herunterladen von Daten in oder aus der Firebase Datenbank kommt, Asynchronität “im Spiel ist”. Der Grund hierfür ist ,dass das Drücken eines Buttons zu jeder Zeit geschehen kann, da die reale Welt ja nicht von einem Takt abhängt. D.h. Button Eingaben werden Parallel zum Hauptthread (GUI) ausgeführt.

Diese Abbildung verdeutlicht dies noch einmal . Wir brauchen hier Asynchronität, weil der Spieler sonst warten müsste, bevor seine Button Eingaben registriert werden, um daraufhin Daten zu manipulieren. Dies ist an sich nicht schlimm, aber in unserem Zeitalter sind wir eben an Multiprocessing und sofortigen Ergebnisse gewöhnt

5. Klassen, Interfaces, Activities und Layouts

Allgemeiner Überblick

Da ich für den Multiplayer zuständig bin, zähle ich erst einmal alle Klassen und Interfaces auf, die ich schreiben musste oder an der ich mitgewirkt habe: Player, Lobby, FirebaseInterface, MultiplayerMenuActivity, CreateGameActivity, InviteListActivity und MultiplayerActivity. Die meisten davon werden jetzt abschnittsweise näher, in obiger Reihenfolge erklärt.

Player:

Diese Klasse wird benötigt um

temporär Spielerdaten in die Firebase abzuspeichern. Methoden gibt es in dieser Klasse keine.

Lobby:

Jetzt wird es interessanter, denn die Lobby Klasse gilt als Herzstück meines Codes. So gut wie alle Klassen und Activites machen von den Lobby Methoden gebrauch. Denn sie hält mithilfe der

FirebaseInterface alle Daten auf dem aktuellsten Stand. Im rechten Schaubild erkennt man die von der Klasse gebrauchten Attribute. Ich werde jetzt näher auf die verwendeten Methoden eingehen.

boolean isInLobby(String player):

Diese Methode ist hilfreich um Redundanzen in der Datenbank zu vermeiden.

void addPlayer(Player player):

Mit der addPlayer Methode fügt man ganz einfach eine Person in die jeweilige Lobby ein. Existiert er/sie schon im Lobby, wird die Methode abgebrochen. Natürlich gibt es auch eine removePlayer() Methode, dort sieht es aber ähnlich aus wie hier.

Boolean isLOBBY_FULL():

Ist wichtig für das Matchmaking, da man ja kein volles Lobby joinen sollte.

Void updateRoomStatus():

Diese Methode wird jedesmal aufgerufen, wenn sich Daten innerhalb der Lobby verändern. Sie sorgt dafür, dass die Anzahl der aktiven Lobby Teilnehmer aktuell bleibt. Zudem erneuert sie auch immer den isLOBBY_FULL Attribut.

FirebaseInterface:

Das FirebaseInterface ist die Klasse, die wie schon der Name zeigt, am meisten mit der Datenbank kommuniziert. Neben der Lobby Klasse ist diese die wichtigste Klasse, da sie an so gut wie allen Datenbank Transfers beteiligt ist.

Damit man aber überhaupt auf die Datenbank zugreifen kann, braucht man neben der von Google erstellten google-services.json Datei noch zahlreiche dependency Implementationen und einen Google Account. Wenn das alles vorhanden ist, kann man eigentlich schon auf seine Datenbank Schreiben/Modifizieren wie man will. Man sollte sich jedoch bewusst sein, wie die Datenbank aufgebaut ist. Ganz einfach erklärt: Firebase speichert alles in der sogenannten JSON Baumstruktur, was soviel bedeutet wie, alle Objekte (die JSON Kompatible sind) werden Nodeweise abgespeichert. Ein Beispiel, hat man schon oben im linken Schaubild der Player Klasse gesehen. Aber ich verdeutliche es hier noch einmal, mit dazugehörigem Code. Rechts sehen sie die Baumstruktur, currentlyOnlinePlayers und Lobbies sind hier die Baumknoten und alles darunter die Blätter.

Am obigen Code erkennt man, dass man sich erst einmal eine Referenz auf die Datenbank besorgen muss, um überhaupt auf die Datenbank zugreifen zu können.

Besteht diese Referenz, kann man auf alle Blätter (auch Children genannt) unter und einschließlich des Baumknotens (auch Parent genannt) zugreifen.

Die Klasse beinhaltet neben simplen Daten Zuweisungen auch mehrere Iterator Routinen, um gewisse Knoten zu durchsuchen wie z.B. die Methoden zur Instandhaltung der currentlyOnlinePlayers Struktur ; unter anderem gehören dazu die add / removePlayerFromOnlineList() Methoden und der sendOnlinePlayerInvite() Methode. In diesen Methoden machen wir auch erstmals Kontakt mit Listenern, Asynchrone Programmstrukturen und des DatasnapShots.

Void sendOnlinePlayerInvite(final String player, final String invitee):

In dieser Klasse wird auch erst einmal vom GenericTypeIndicator Gebrauch gemacht. Dies wird wie man sich schon denken kann, bei generischen Collections gebraucht, um deren Datentyp leicht auf den Linkswert anzupassen.

Da hier keine Daten laufend aktualisiert werden müssen, wende ich hier den .addListenerForSingleValueEvent an, um einmalig eine Einladung abzuschicken. Natürlich kann man den sendOnlinePlayerEvent() noch öfters hintereinander abrufen wenn man will. Firebase Listeners enthalten generell immer die verschachtelten Methoden; onDataChange und onCanceled. In der onDataChange Methode spielt sich die meiste Musik ab, mit Hilfe des DataSnapshots lässt es leicht Datenbank Zustände abrufen, diese werden aber nicht aufgrund der .addListenerForSingleValueEvent() Bedingung, in Echtzeit aktualisiert, sondern man hat immer nur einen Datenauschnitt, ab dem Punkt an dem man .addListenerForSingleValueEvent() abruft. Die Daten die jetzt im dataSnapshot

enthalten sind lassen sich frei manipulieren aber ACHTUNG! Möchte man diese Werte außerhalb des Listener Scopes abspeichern, trifft man auf lästige Hürden. Da es sich um die Asynchrone Datenbeschaffung handelt, ist es nicht immer garantiert, dass DataSnapshot überhaupt Daten in sich trägt, daher wird dieses aus dem Scope Speicher strikt vom Compiler unterbunden. Man sollte daher versuchen mit der Asynchronen Struktur zu arbeiten, statt dagegen. D.h. man sollte nur temporäre Daten in diesen Asynchronen Strukturen behandeln. Zum Schluss kann man diese veränderten Daten, wieder hochladen wenn man möchte, ich habe mich oft dafür entschieden, damit meine Daten immer auf den aktuellsten Stand sind.

MultiplayerMenuActivity:

Activities lassen sich recht schnell erklären. Man kann sich es so denken, dass man mehrere GUIs

in einer Nodelist aufbewahrt. Mit der Betätigung eines Buttons, dem Drücken eines physikalischen Knopfes, dem Finger Wischen usw. begibt man sich entweder tiefer oder höher in der Nodeliste, bis man an das Ende angekommen ist und keine Nodes mehr vorhanden sind. Nur dass diese Nodes anders heißen, und zwar Activity. Man begibt sich von einer Activity in die Nächste. Man kann sich Buttons erstellen, wie man im rechten Bild erkennt. Jeder Button kann mit Hilfe eines onClickListeners() zum Leben gebracht werden. Man kann in unserem Fall eine Spielsitzung eröffnen, eine existierende Spielsitzung beitreten, Einladungen betrachten usw.

Im unteren Schaubild erkennt man ein Bsp. zur Implementierung eines Automatching Buttons.

Unten im Quellcode erkennt man an der startActivity() Methode, dass man von MulitplayerMenuActivity, in die Lobby wechselt.

InviteListActivity

Für die Einlade Liste habe ich mir

gedacht, dass eine ListView sich am besten dafür eignet, mit zusätzlichem Akzeptier Button, welcher nach dem Antippen den Spieler direkt in die eingeladene Spielesitzung schickt .

Das ist nur ein Bsp., aber trotzdem wichtiger Komponent um ListViews überhaupt erst ermöglichen zu können, denn damit kann man Collections mit dem Layout und deren dazugehörigen variablen populieren.