fop-projekt/doc/Dokumentation.tex
Dennis Weinberger 03b59c3e98 Update Dokumentation.tex
subsection zu subsubsection
2019-03-25 22:25:50 +00:00

351 lines
12 KiB
TeX

\documentclass{article}
\usepackage{amsmath}
\usepackage{algorithm}
\usepackage[noend]{algpseudocode}
\usepackage{hyperref}
\usepackage[utf8]{inputenc}
\title{FOP Projektgruppe 175}
\author{Steffen Wagner\\
Dennis Weinberger\\
Jonas Süß\\
Joachim Schmidt}
\begin{document}
\maketitle
\tableofcontents
\pagebreak
\section{Der Graph}
\subsection{Bildung der Kanten}
Der Algorithmus für die Bildung der Kanten ist eine
Version des \href{https://de.wikipedia.org/wiki/Algorithmus_von_Prim}{Algorithmus von Prim}:
\begin{algorithm}
\caption{Bildung von Kanten}\label{generate}
\begin{algorithmic}[1]
\Procedure{generateEdges}{}
\If{nodes is empty} return \EndIf
\State $castle \gets allCastles[0]$
\State $remainingCastles \gets allCastles$
\State $connectedCastles \gets empty$
\Loop
\If{$remainingCastles$ is empty} break
\EndIf
\State connect $castle$ to nearest castle
\State remove $castle$ from $remainingCastles$
\State add $castle$ to $connectedCastles$
\EndLoop
\ForAll{castle in allCastles}
\ForAll{nearCastle in allCastlesInRadius(castle)}
\State connect $castle$ to $nearCastle$
\EndFor
\EndFor
\EndProcedure
\end{algorithmic}
\end{algorithm}
% Erklärung des Algorithmus
Der Algorithmus ist in zwei Schritte aufgeteilt:
\begin{itemize}
\item Minimale Verbindung von allen Burgen
\item Ästhetische Verbesserung der Kanten
\end{itemize}
Die minimale Verbindung aller Burgen erfolgt, indem
sichergestellt wird, dass jede Burg mit einer anderen
verbunden ist und dass alle Burgen in einer gemeinsamen
Verbindung zusammenhängen. Der Algorithmus fängt bei
einer bestimmten Start-Burg an und verbindet diese Burg
mit der nächstliegenden Burg, die noch nicht verbunden
wurde. Daraufhin wird das gleiche mit der nächsten,
übernächsten, usw. Burg getan, bis die letzte Burg erreicht
wurde. Zu diesem Zeitpunkt sind alle Burgen durch einen
Graph verbunden.
Die Ästhetische Verbesserung erfolgt, indem alle Burgen
im Umkreis einer Burg durch eine Kante verbunden werden.
\subsection{Überprüfung der Erreichbarkeit aller Knoten}
Der Algorithmus, der prüft, ob alle Knoten erreichbar sind, ist
folgender:
\begin{algorithm}
\caption{Erreichbarkeit aller Knoten}\label{connected}
\begin{algorithmic}[1]
\Procedure{allNodesConnected}{}
\State $\textit{firstNode} \gets \text{first element of }\textit{nodes}$
\State $allVisitedNodes \gets \textit{empty}$
\State $nextVisitNodes \gets empty$
\State $\text{append } firstNode \text{ to } allVisitedNodes$
\State $\text{neighborsOf } firstNode$
\State $\rightarrow \text{filter out all } x \text{ where } allVisitedNodes \text{ contains } x$
\State $\rightarrow \text{append to } nextVisitNodes$
\Loop
\If {$nextVisitNodes \text{ is empty}$}
break
\EndIf
\State $\text{append first element of } nextVisitNodes \text{ to } allVisitedNodes$
\State $\text{neighborsOf first element of } nextVisitNodes$
\State $\rightarrow \text{filter out all } x \text{ where } allVisitedNodes \text{ contains } x$
\State $\rightarrow \text{append to } nextVisitNodes$
\State $\text{delete first element of } nextVisitNodes$
\EndLoop
\EndProcedure
\end{algorithmic}
\end{algorithm}
Der Algorithmus verwendet zwei unterschiedliche Datentypen:
\begin{itemize}
\item HashSet wird verwendet, um die bisher
besuchten Knoten zu speichern. Eine HashSet
hat den Vorteil, dass Elemente nur einmal
gespeichert werden können.
\item ArrayDeque wird verwendet, um die nächsten
Knoten, die besucht werden, zu speichern.
\end{itemize}
Der Algorithmus sammelt sozusagen alle Knoten, die
aufgrund von momentanen Erkenntnissen erreichbar
sind, in der ArrayDeque \texttt{nextVisitNodes}. Hingegen sind
alle Knoten, die schon erreicht worden sind, in dem HashSet
\texttt{allVisitedNodes} gespeichert.
Der Algorithmus geht die ArrayDeque \texttt{nextVisitNodes}
solange durch, bis diese leer ist. In jeder Iteration wird
das erste Element der Liste aus der Liste entfernt.
Zunächst wird dieses Element dem HashSet \texttt{allVisitedNodes}
hinzugefügt. Daraufhin
werden die Nachbarn dieses Elements herausgefunden. Diejenigen
Nachbarn, die schon in dem HashSet \texttt{allVisitedNodes}
vorhanden sind, werden verworfen. Die restlichen Nachbarn
werden der ArrayDeque \texttt{nextVisitNodes} hinzugefügt.
\subsection{Wege finden}
\subsubsection{Teil (a)}
Eine pseudocode-Darstellung des Algorithmus:
\begin{algorithm}
\caption{Berechnung der Distanzen}\label{path}
\begin{algorithmic}[1]
\Procedure{run}{}
\State $v \gets$ getSmallestNode()
\Loop
\If{$v$ is null} \Return \EndIf
\ForAll{edges}
\If{edge is passable}
\State $n \gets$ otherNode
\State $a \gets$ v.value + edge.value
\If{n.value $= -1$ or n.value $> a$}
\State n.value $\gets$ a
\State n.previous $\gets$ v
\EndIf
\EndIf
\EndFor
\State $v \gets getSmallestNode()$
\EndLoop
\EndProcedure
\end{algorithmic}
\end{algorithm}
Die algorithmische Komplexität im worst-case ist:
$$O(n^2)$$
$n$ soll in diesem Fall die Anzahl an Knoten wiedergeben.
Da es sich bei \texttt{availableNodes} um eine
\texttt{LinkedList<Node<T>>} handelt, muss bei jedem
Durchgehen der Liste jedes Element einzeln abgearbeitet
werden. In der Funktion \texttt{getSmallestNode()} ist
dies der Fall.
Das bedeutet, dass bei jeder Aufruf von \texttt{getSmallestNode()}
eine Komplexität von $O(n)$ hat, wenn $n$ die Anzahl
der Knoten darstellt.
Bei jeder Iteration des Algorithmus wird zuerst der
Knoten mit dem kleinsten Wert gesucht. Dann werden alle
Nachbarn dieses Knotens abgeprüft. Das können höchstens
$n - 1$ Knoten sein, da ausgeschlossen wird, dass ein
Knoten eine Kante mit sich selber haben kann.
\subsubsection{Teil (b)}
Anstelle einer \texttt{LinkedList} braucht man eine
Datenstruktur, die den Zugriff auf das kleinste Element
effektiver gestaltet.
Ein Beispiel dafür ist die \texttt{PriorityQueue},
die wir in Hausübung 8 implementiert haben. Dort werden
Elemente beim Einfügen bereits sortiert, sodass der
Zugriff auf das (in diesem Fall) kleinste Element
in $O(1)$, also konstanter Zeit, gemacht werden kann.
Die algorithmische Komplexität mit dieser Änderung
beträgt dann im worst case
$$O(n)$$
Für einen allgemeine Datentyp \texttt{T} kann man
folgendes feststellen:
Sei $x$ die algorithmische Komplexität für das
Suchen vom kleinsten Element (z.B. $x = O(n)$ bei
\texttt{LinkedList}).
Sei $y$ die algorithmische Komplexität für das
Entfernen vom kleinsten Element (bei einer normalen
\texttt{LinkedList} $O(1)$).
Dann beträgt die algorithmische Komplexität
$$O(n \cdot x \cdot y)$$
\subsubsection{Teil (c)}
Sei $n$ die Anzahl an Knoten.
\textbf{Invariante}: Nach $h \geq 0$ Durchläufen gilt:
\texttt{availableNodes}, die Liste von Knoten, die
noch nicht bearbeitet wurden enthält $n - h$ Elemente.
Diese Knoten sind alle Knoten ohne die $h$ kleinsten
Knoten, also alle $n - h$ größten Knoten.
Für alle noch nicht abgearbeiteten Knoten gilt:
$n - h$ Elemente wurden noch nicht abgearbeitet.
Für alle abgearbeiteten Knoten gilt:
$h$ Knoten sind schon abgearbeitet worden.
Die zu den Knoten zugehörigen \texttt{AlgorithmNodes}-Objekte
beinhalten die insgesamte Länge zu dem Startknoten.
\subsection{Kürzester Pfad zu allen Knoten}
\subsubsection{Teil (a)}
Wenn ein Zykel auftaucht, dann bedeutet
das, dass eine Menge $K$ an Knoten gibt, sodass
$K = \{k_1 ... k_n\}$ und Kanten zwischen $k_1$
und $k_2$ usw. bis $k_n$ und $k_1$ existieren.
Ein negativer Zykel ist dann vorhanden, wenn
$M^0(k_1, k_2) + ... + M^0(k_n, k_1) < 0$.
Oder mathematischer aufgeschrieben:
$$\sum_{l=1}^{n-1} {M^0(k_l, k_{l+1})} + M^0(k_n, k_1) < 0$$
Laut der Schleifeninvariante gilt, dass nach $h \geq 0$
Schritten $M^h$ die Längen von den Pfaden enthält,
die am kürzesten sind und dabei nicht mehr als
$h + 1$ Kanten besuchen.
%Für die Graphmatrix $M$ gilt außerdem
%$M(k_1, k_2) < 0$ usw. bis $M(k_n, k_1) < 0$.
\subsubsection{Teil (b)}
Best case: $\Theta()$
Worst case: $\Theta()$
\section{Weitergestaltung des Spiels}
\subsection{Computergegner}
\subsection{Missionen}
\subsubsection{Begrenzte Rundenanzahl}
Wenn man diese Mission wählt, gilt es, nach einer
festgelegten Anzahl an Runden die meisten Burgen
zu besitzen. Sollten zwei oder mehr Spieler
gleich viele Burgen zu diesem Zeitpunkt in Besitz haben,
gibt es ein Unentschieden.
\subsubsection{Capture the Flag}
In dieser Mission werden wichtige Burgen, sogenannte
Flags, gleichmäßig auf die Spieler verteilt. Es ist das
Ziel, zuerst vor allen anderen Spielern alle diese Burgen
zu erobern.
Zur Erstellung dieser Mission wurde die Klasse
\texttt{CaptureTheFlagGoal} erstellt.
Diese Klasse behandelt die gesamte Logik der
Mission. Zu diesen Aufgaben gehört nicht nur die
Überprüfung, ob zu einem Zeitpunkt ein Spieler
gewonnen oder verloren hat, sondern auch bei dieser
Mission die Verteilung der Flaggen-Burgen auf die Spieler.
Das wird dann gemacht, sobald alle Burgen auf
die Spieler aufgeteilt wurden. Dann werden
per Zufallsprinzip die Burgen ausgewählt.
Zur vollständigen visuellen Darstellung dieser Mission
wurde noch die Klasse \texttt{MapPanel} erweitert.
Nun wird bei jedem Aufruf von \texttt{ImagePanel.paintComponent}
eine weiße Umrandung um die Flaggen-Burgen gezeichnet,
wenn die Mission aktiv ist und die Burgen verteilt wurden.
\subsubsection{Bevölkerung}
Das Ziel der Spieler, die diese Mission ausgewählt haben,
ist es, vor allen anderen Spielern eine bestimmte Anzahl
an Truppen auf verschiedenen Burgen stationiert zu haben.
\subsection{Joker}
Wir haben zwei Joker in unser Spiel eingebaut.
Diese sind in einem Panel über dem MapPanel auswählbar.
\subsubsection{Truppen-Joker}
Der Truppen-Joker hat eine ganz einfache Funktion:
Der Spieler, der ihn benutzt, bekommt fünf neue Truppen zum Verteilen.
Der Truppen-Joker kann nur einmal verwendet werden.
Man findet ihn direkt über der Karte oben links.
Der Truppen-Joker ist mit einem braunen Plus dargestellt.
\subsubsection{Truppen-Verscheuchen-Joker}
Ein etwas kreativerer Joker ist der Truppen-Verscheuchen-Joker.
Dieser befindet sich ebenfalls im Joker-Bereich über der Map an
zweiter Stelle rechts neben dem Truppen-Joker. Der Spieler, der den
Truppen-Verscheuchen-Joker benutzt, darf sich eine gegnerische Burg
auswählen. Aus dieser Burg fliehen dann alle Truppen in benachbarte
Burgen des Spielers, dem die ausgewählte Burg gehört.
Die Aufteilung auf die benachbarten Burgen ist dabei zufällig.
Falls es nur eine benachbarte Burg des gleichen Spielers gibt,
fliehen alle Truppen dorthin. Falls es keine benachbarte Burg
des gleichen Spielers gibt, d.h. die Burg isoliert ist, so bleibt
bis auf eine Truppe niemand in dieser Burg übrig.
Der Truppen-Verscheuchen-Joker ist mit einem
erschreckten Pixel-Gesicht dargestellt.
Der Truppen-Verscheuchen-Joker erlaubt eine Vielzahl an neuen
Strategien im Spiel, weshalb wir uns trotz der etwas
schwereren Implementation dafür entschieden hatten,
ihn einzubauen. So ermöglicht er es z.B. einen Spieler,
der nur auf einer einzelnen Burg alle seine Truppen behaust,
zu bestrafen. Diese kann man umzingeln und dann den Joker einsetzen.
Vor allem ist das nützlich, wenn nur noch eine einzelne Burg nicht
erobert wurde, und das Spiel sich sonst lange ziehen würde, d.h.
dieser Joker kann das Spiel ebenso beschleunigen.
Umgekehrt müssen Spieler nun darauf achten, Truppen möglichst gleichmäßig
zu verteilen, um dieser Taktik entgegenzuwirken, und Burgen möglichst
zusammenhängend zu erobern, damit im Falle des Joker-Einsatzes die Truppen
zumindest nicht komplett verloren gehen, sondern in eine andere Burg wandern.
Der Joker erhöht also auch die Spielkomplexität und
macht das Spiel damit insgesamt interessanter.
\end{document}