-
Notifications
You must be signed in to change notification settings - Fork 0
/
project.tex
205 lines (154 loc) · 12.9 KB
/
project.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
\chapter{Projekt}
\paragraph*{}
Ziel unseres Projektes war es, eine Implementierung des Spielklassikers Pong auf dem Mikrocontroller zu schaffen. Das Projekt kann in 4 Unterabschnitte unterteilt werden. Als erstes versuchten wir ein angeschlossenes Oszilloskop über die DA-Wandler als Bildschirm zu verwenden. Darauf implementierten wir die Spiellogik. Anschließend erscheine es sinnvoll weitere zwei Controller zur Steuerung zu nutzen, damit die Spieler über mehr Bewegungsfreiheit verfügen und die Technik nicht leidet. Dafür entwickelten wir ein primitives Protokoll mit dessen Hilfe alle Spieldaten über die Transiver ausgetauscht werden können. Zu letzten beschäftigten wir uns mit der Steuerung selbst.
\section*{Grafikausgabe}
\paragraph*{}
Um die Ausgabe auf dem Oszilloskop umzusetzen sahen wir grundsätzlich mehrere Möglichkeiten. Gemein ist bei allen Ansätzen, dass zu einem Zeitpunkt, auf Grund der Konstruktionsweise des Oszilloskops, nur ein Punkt dargestellt werden kann. Daher wird es nötig ein Bild zu zeichnen, in dem in einem für das menschliche Auge nicht auflösbarem Zeitintervall alle Bildpunkte einmal angesteuert werden (bestenfalls sogar mehrfach).
\paragraph*{}
Um Formen darzustellen bieten sich grundsätzlich zwei Möglichkeiten: Es können entweder Punkte oder Pfade gespeichert werden. Da Pfade bei der Darstellung auf dem Oszilloskop sowieso wieder in Punkte umgewandelt werden müssen, und anders als bei herkömmlichen Computern keine vorgefertigten Lösungen in Hardware dafür existieren, entschieden wir uns, gleich mit Punkten zu arbeiten.
\paragraph*{}
Als letztes mussten wir noch entscheiden wie wir die Punktmengen effizient speichern und gleichzeitig die resultierenden Bilder effizient aufbauen können. Hierbei können Punkte entweder in Matrizen von Wahrheitswerten, wobei die Indizes den Koordinaten entsprechen, oder gleich als Liste von Koordinatenpaaren gespeichert werden. Ersteres ist besonders von Vorteil, wenn es für die Performance wichtig ist benachbarte Bildelemente zeitnahe darzustellen. Allerdings wird in unserem Anwendungsfall eine große Menge an Feldern existieren, die nicht vorhandene Punkte beschreiben und trotzdem abgearbeitet und gespeichert werden müssen. Da wir theoretisch wahlfreien Zugriff auf der Mattscheibe des Oszilloskops haben und der Speicher des Controllers sehr beschränkt ist, entschieden wir uns die Bilder mittels Punktlisten zu speichern. Die Realität zeigte das die Bildqualität von der Lokalität der aufeinander folgender Punkte profitieren kann, da es dann weniger nach leuchtende Stahlbewegungen auf dem Bildschirm gibt (vergleiche endgültige Lösung).
\paragraph*{}
Zur Umwandlung unserer Koordinaten in Spannungswerte, mit denen das Oszilloskop angesteuert werden kann, nutzen wir die zwei Digital-Analogwandler unseres Controllers. Die Umwandlung sollte gleichmäßig und dauerhaft erfolgen, um ein möglichst gute Bildqualität zu erreichen (insbesondere kein Stocken oder Aussetzen des Bildes). Da das Hauptprogramm noch Berechnungen zum eigentlichen Spiel berechnen soll, erscheint es sinnvoll auf die eingebaute DMA zurückzugreifen um die Umwandlung mit neuen Eingabedaten zu versorgen und zu damit zu starten. Wir testeten verschiedene Konstellationen und Modi der DMA. Insbesondere die Wahl des Interrupts der die DMA zum Übertragen der neuen Werte auffordert stellte sich als Schwierigkeit heraus. Es muss eine Abwägung statt finden zwischen, der Anzahl der Punkte (und damit auch Bilder) die pro Sekunde gezeichnet werden und der für das Hauptprogramm verfügbaren Rechenzeit, da die CPU während der DMA Aktivität angehalten wird. Nach versuchen das Ende einer Wandlung zu nutzen entschieden wir uns letztendlich auf Grund der guten Steuerbarkit und zu Gunsten der Rechenzeit im Hauptprogramm für einen Timer geschützte Lösung.
\paragraph*{}
Da zu diesem frühen Zeitpunkt noch keine Daten zum Ausgeben zur Verfügung standen nutzten wir auf Empfehlung die Lissajou-Figuren um unsere Darstellung zu testen. Die sind Schwingungen von zwei Trigonometrischen Funktionen zweidimensional aufgetragen. Je nach Frequenzverhältnis entstehen dabei verschiedene Schwingungen, wobei einige Aussehen wir räumliche Objekte in Drehbewegungen.
\lstinputlisting[caption=Lissajous Figuren]{src/project_lissajou.c}
Diese Simulation unserer späteren Umsetzung hat vor allem den Vorteil, das im Hauptprogramm fortwährend die nächste Darstellung der Funktion berechnet wird und wir so gut abschätzen konnten, wie viel Rechenzeit zwischen den Umwandlungen zur Verfügung steht.
\section*{Spiellogik}
\paragraph*{}
Wir implementieren, bis auf eine Ausnahme - das Lebensystem, das Spiel Pong in der klassischen Variante. Das Feld wird mit 1024 mal 1024 Punkten angenommen. Der Ball und sein momentaner Status wird durch ein struct vom Type Ballstate repräsentiert, während die Bars durch den Barstate codiert werden (dieser enthält als einzige Information die Höhe der Bars gegenüber 0).
\paragraph*{}
Die Funktion {\em next()} errechnet die nächste Zeititeration des Spieles. Sie erhält einen Ballstate und die beiden Barstates, aus Geschwindigkeit, Richtung und Postion des Balls ermittelt sie, wo er sich zum nächsten Zeititeration befinden würde. Der Winkel wird dabei als Bogenmaßgespeichert. Anschließend überprüft sie Kollisionen mit dem Rand oder ob die linke und rechte Spielfeldseite übertreten wurde. Im letzteren Fall wird überprüft ob sich ein Schläger in ausreichende Nähe befindet. Aus dem Schnittpunkt und -winkel zwischen Bande und Ball wird die neue Ballpostion errechnet. Dabei wir angenommen das der Einfallswinkel dem Ausfallwinkel entspricht, nur bei der Kollision mit den Schlägerrändern wird der Ball mehr weg reflektiert. Zusätzlich erhöht jeder Schlag die Geschwindigkeit des Balls. Der Rückgabewert der Funktion informiert, ob der Ball im Feld ist und wenn nicht die auf welcher Seite er ausgegangen ist. Ist der Ball aus, wird er zurückgesetzt zum Aufschlagpunkt und dem entsprechenden Spieler ein Punkt abgezogen.
\begin{lstlisting}[caption=Kernkomponeten der Spiellogik]
// Diese Funktion berechnet die naechste Position des Balls
int next(ballstate *ball, barstate bar_left, barstate bar_right)
{
float m_x, m_y;
// Richtungsvektoren der Bewegung berechnen
m_x = ball->speed * cos(ball->angle);
m_y = ball->speed * sin(ball->angle);
// Vektoren zur aktuellen Position addieren
ball->x += m_x;
ball->y += m_y;
// Kollisionskontrolle
// Ball hat einen Rand des Spielfeld erreicht
// Refelktion berechnen
if ((ball->y <= 0) || (ball->y >= FIELD_Y)) {
ball->angle = normalize_radiant(2*M_PI - ball->angle);
if (ball->y >= FIELD_Y) {
// Ball trifft Wand bei yMax
ball->y = 2 * FIELD_Y - ball->y;
}
else {
// Ball trifft Wand bei yMin
ball->y = fabs(ball->y);
}
// Geschwindikeit des Balls erhoehen (mehr Spass)
ball->speed += 0.5;
}
// Ball ist auf einer Spielerseite angekommen
if(ball->x <= 0 || ball->x >= FIELD_X){
// Variable zur bestimmung Wo der Schlaeger getroffen wurde
// um den Reflektionswinkel davon abhaenging zu machen
float bar_position;
if (ball->x <= 0) {
float collision_point_y = fabs(tan(ball->angle) * (ball->x - m_x) + (ball->y - m_y));
bar_position = collision_point_y - bar_left;
// Ball trifft Ebene bei xMin
ball->x = fabs(ball->x);
}
else {
float collision_point_y = fabs(tan(ball->angle) * (FIELD_X - ball->x - m_x) + (ball->y - m_y));
bar_position = bar_right - collision_point_y;
// Ball trifft Ebene bei xMax
ball->x = 2 * FIELD_X - ball->x;
}
if (fabs(bar_position) <= (BARLENGTH/2.0)) {
ball->angle = normalize_radiant((M_PI - ball->angle) + (bar_position / (BARLENGTH/2.0)) * GRAD(30.0));
// Dafuer sorgen das der Reflektionswinkel nicht zu
// steil wird
if (ball->angle > GRAD(70.0) && ball->angle < GRAD(110.0)) {
if (ball->angle > GRAD(90.0)) {
ball->angle = GRAD(110.0);
}
else {
ball->angle = GRAD(70.0);
}
}
else if (ball->angle > GRAD(250.0) && ball->angle < GRAD(290.0)) {
if (ball->angle > GRAD(270.0)) {
ball->angle = GRAD(290.0);
}
else {
ball->angle = GRAD(250.0);
}
}
// Geschwindigkeit des Ball erhoehen und getroffen
// zurueckgeben wenn der Ball getroffen wurde
ball->speed += 1;
return IN;
}
// Entscheiden welche Seite Schuld war im Falle, dass der
// Ball nicht getroffen wird
if (ball->x > (FIELD_X / 2.0)) {
life_left--;
return LEFT;
}
life_right--;
return RIGHT;
}
return IN;
}
\end{lstlisting}
\paragraph*{}
Wir verwenden kein Punkte- , sondern ein Lebensystem. Jeder Spieler erhält 3 Leben am Anfang des Spieles. Sobald der Ball auf seiner Seite aus geht wird ihm ein Leben abgezogen. Es wird solange gespielt bis ein Spieler eine negative Anzahl an Leben hat.
\newpage
\section*{Kommunikationsprotokoll}
\paragraph*{}
Um die Komunikation zwischen dem Server und den Clienten möglichst einfach und dynamisch zu gestallten, haben wir ein simples Protokoll entworfen welches im großen und ganzen aus den folgenden Nachrichten besteht.
\lstset{
frame=none,
}
\begin{longtable}{p{0.4\textwidth}p{0.5\textwidth}}
\textbf{Nachricht} &
\textbf{Wirkung}
\endhead
\hline
\begin{lstlisting}
W
\end{lstlisting} &
Waiting for Players - Wird vom Server gesendet wenn er noch nicht genug Spieler hat um ein Spiel zu starten. Ein Client antwortet auf diese Nachricht mit R wenn er Spielen möchte. \\
\hline
\begin{lstlisting}
R
\end{lstlisting} &
Ready - Ein Client schickt R wenn er ein W von einem Server erhält und sich im Bereitschaftsmodus befindet. \\
\hline
\begin{lstlisting}
C<1 digit number>
\end{lstlisting} &
Connected - Ein Server schickt einem Client diese Nachricht wenn er sein R Packet erhalten hat und ihn als Spieler registriert hat. Die einstellige Zahl welche mitgeschickt wird, ist der Kanal, welcher dem Spieler zugewiesen wurde. \\
\hline
\begin{lstlisting}
L<1 digit number>
\end{lstlisting} &
Lifes - Der Server schickt diese Nachricht an einen Clienten um ihm mitzuteilen wieviel leben er Anzeigen soll. Ein Client interpretiert die erste L Nachricht als Spielstart und beginnt P Nachrichten zu schicken. \\
\hline
\begin{lstlisting}
P<3 digit Number>
\end{lstlisting} &
Position - Ein Client schickt periodisch diese Nachrichten, sobald das Spiel, läuft um dem Server die gewünschte Position des Schlägers mitzuteilen. \\
\hline
\end{longtable}
\lstset{
frame=tblr,
}
Ein Spiel würde folgendermaßen ablaufen. Der Server wird eingeschaltet und schickt W nachrichten um Clienten auf sich aufmerksam zu machen. Ein Client wird per Knopfdruck in den Bereitschaftsmodus versetzt, beantwortet die W Nachricht mit einer R Nachricht und erhält eine C Nachricht mit seinem Kanal. Ein zweiter Client tut das selbe und erhält ebenfalls einen Kanal. Nun hat der Server 2 Spieler zur verfügung und Startet einen Countdown. Am Ende dieses Countdowns schickt er jedem Clienten eine L Nachricht mit der Initialmenge Leben. Die Clienten wissen dann, dass das Spiel begonnen hat und schicken P Nachrichten.
\section*{Steuerung}
\paragraph*{}
Jeder der beiden Spieler nutzt einen zweiten Controller um am Spiel teilzunehmen. Die LEDs des Controllers erläutern dem Spieler vor dem Spiel, ob er verbunden ist oder nicht. Während des Spiels werden die Leben des Spielers angezeigt.
\paragraph*{}
Die Steuerungsmöglichkeiten vor Spielbeginn beschränken sich auf den Druck auf einen der beiden Tasten. Dies starte die Suche nach eine Server. Während des Spiels kann jeder Spieler seinen Schläger (Bar) über die Beschleunigungssensoren steuern. Dabei ist die Steuerung nicht relativ zur Postion des Schlägers, sonder absolut. Es wird die Neigung des Controllers gegenüber der Erdbeschleunigung gemessen. Die Formeln für diese Berechnung wurden uns gegeben. Um starkes Wackeln der Schläger zu unterbinden, welches durch die Ungenauigkeit der Beschleunigungsensoren oder minimale Schwanken entstehen kann, mitteln wir den Wert über mehrere Messungen und das letzte Ergebnis. Dadurch entsteht eine angenehme und akkurate Steuerung.
\section*{Abschluss}
\paragraph*{}
Als Resultat unseres Projekts entstand ein durchaus spielbares Pongspiel, welches genutzt werden kann wenn der Clientcode auf zwei und der Servercode auf einen Controller geflasht wird. Der vollständige Code erschien zu umfangreich um ihn in dieses Protkoll mit ein zu schließen und ist deßhalb nur im Anhang unserer Abgabemail enthalten.