Pong für zwei Spieler
mit Arduino Mega und 64x64-Matrix
Das Spiel Pong ist keine Neuheit, man könnte auf die Beschreibung der Regeln praktisch verzichten. Das Spiel ähnelt dem Tennis-Spiel und heißt
hier „für zwei Spieler“, da wir mit einer 1-Spieler-Version bereits experimentiert haben. Damals ging es speziell um Joystick-Ansteuerung, die mit Arduino Uno
realisiert wurde. Das Beispiel ist hier zu finden:
In dem folgenden Experiment kommt eine größere Matrix (64x64 Pixel) zum Einsatz. Die Matrix-Steuerung übernimmt Arduino Mega. Die dazugehörige Schaltung ist
äußerst einfach. Neben den zwei bereits erwähnten Komponenten nehmen an der Schaltung noch zwei Potis und ein Taster teil. Das ist schon alles.
Für diejenigen, die mit Pong doch noch nicht in Berührung gekommen sind, kurz die Regeln. Über das Spielfeld bewegt sich ein Ball, der durch einen einzelnen
Pixel symbolisiert wird. Jeder Spieler verfügt über einen Schläger, den er rauf und runter bewegen kann. Die Schläger werden durch 6-Pixel Linien symbolisiert und
befinden sich auf den gegenüberliegenden Spielfeldseiten. Immer wenn der Ball die Spielfeldseite eines Spielers erreicht, muss er versuchen, den Ball mit dem
Schläger zurückzuschlagen. Gelingt ihm das nicht, bekommt der Gegner einen Punkt. In unserem Fall gewinnt der Spieler, der als erster 50
Punkte einsammelt.
Schaltplan
Testschaltung
Programm (Sketch)
// ************************************************************************************************
// Pong für zwei Spieler
// Eine Schaltung mit Arduino Mega und Matrix 64x64
// Arduino IDE 2.3.5
// ************************************************************************************************
#include "RGBmatrixPanel.h" // Bibliothek Einbindung
#define CLK 11
#define OE 9
#define LAT 10
#define A A0
#define B A1
#define C A2
#define D A3
#define E A4
RGBmatrixPanel matrix(A, B, C, D, E, CLK, LAT, OE, false, 64);
unsigned long Speed_Time = 30; // Tempo Bewegung Ball
unsigned long Millis_Alt; // Zeit festhalten
unsigned long Millis_Aktuell; // Zeit aktuell
int Schlaeger_Soll_Pos [2]; // Schläger Y-Pos
int Pos_Alt [2]; // Alte Schläger Y-Pos
int Richtung = 2; // Bewegungsrichtung des Balles
// Nächste X/Y-Position
int X_Datensatz [14] = { 1, 1, 2, 1, 2, 1, 1, -1, -1, -2, -1, -2, -1, -1 };
int Y_Datensatz [14] = { -2, -1, -1, 0, 1, 1, 2, 2, 1, 1, 0, -1, -1, -2 };
int X = 32,Y = 20; // Ball Koordinaten
int Analoge_Werte [2][10]; // Poti Werte
int Mittelwert [2]; // Poti Mittelwerte
int Zufall_Richtung; // Zufallszahl -1/0/1
int Start_Stop = 8; // Taster Start / Stop
int Punkte [2]; // Punkte aktuell
int Punkte_Alt [2]; // Alte Punktzahlen
bool Spiel_laeuft; // Merker
// ************************************************************************************************
void setup() {
matrix.begin();
delay(500);
matrix.fillRect(10, 57, 40, 7, matrix.Color333(0, 0, 0)); // Matrix löschen
matrix.drawRect(0, 0, 64, 57, matrix.Color333(0, 0, 7)); // Spielfeld markieren
matrix.setTextSize(1); // Textgröße Punkteanzeige
pinMode(Start_Stop, INPUT_PULLUP); // Taster Spiel Start / Stop
Pos_Alt [0] = 25; // Y Vorbelegung
Pos_Alt [1] = 25;
}
// ************************************************************************************************
void loop() {
// Spiel starten oder stoppen
if (digitalRead (Start_Stop) == LOW or Punkte [0] == 50 or Punkte [1] == 50) {
Spiel_laeuft = not Spiel_laeuft;
if (Spiel_laeuft) {
matrix.fillRect(0, 57, 64, 7, matrix.Color333(0, 0, 0));
matrix.fillRect(0, 57, 2, 7, matrix.Color333(7, 0, 0));
Punkte_Anzeige ();
} else {
matrix.fillRect(0, 57, 2, 7, matrix.Color333(0, 0, 0));
}
Punkte [0] = 0;
Punkte [1] = 0;
delay (1000);
}
Analoges_Mittelwert (); // Potiwerte ermitteln
Schlaeger_bewegen (); // balken bewegen
Millis_Aktuell = millis(); // Zeit für Tempo der Ballbewegung
if ((Millis_Aktuell - Millis_Alt) > Speed_Time) {
Millis_Alt = Millis_Aktuell;
// Ball Bewegung anzeigen
if ((Y > 0 && Y < 56 && X < 61 && X > 2)) { // Rand nicht bechreiben
matrix.drawPixel(X, Y, matrix.Color333(0, 0, 0)); // Alte Position löschen
}
X = X + X_Datensatz [Richtung]; // Neue Koordinaten
Y = Y + Y_Datensatz [Richtung];
if ((Y > 0 && Y < 56 && X < 61 && X > 2)) { // Rand nicht bechreiben
matrix.drawPixel(X, Y, matrix.Color333(0, 7, 0)); // Ball anzeigen
}
if ((Y < 2) or (Y > 55)) { // Rand Oben/Unten erreicht?
if (Richtung < 7) { // Bewegung von Links nach Rechts
Richtung = 6 - Richtung; // Neue Richtung
}
if (Richtung > 6) { // Bewegung von Rechts nach Links
Richtung = 7 - Richtung + 13; // Neue Richtung
}
}
Zufallszahl:
int Z = random (-1,2); // Richtung Faktor via Zufall
if (Z == Zufall_Richtung) {
goto Zufallszahl;
}
Zufall_Richtung = Z;
Punkte_Alt [0] = Punkte [0];
Punkte_Alt [1] = Punkte [1];
if (X > 59) { // Rechte Feldgrenze erreicht?
Richtung = 13 - Richtung; // Folgerichtung Grundregel
Punkte [0]++; // Punkt für LINKS
if (X == 61) { X = 60;}
// Schläger getroffen ?
if (Y >= Schlaeger_Soll_Pos [1] && Y <= Schlaeger_Soll_Pos [1] + 5) {
Richtung = Richtung + Zufall_Richtung; // Folgerichtung bestimmen
if (Richtung > 13) { Richtung = 11; } // Richtung Grenzen
if (Richtung < 7) { Richtung = 9; }
Punkte [0]--; // Kein Punktverlust
}
Punkte_Anzeige ();
}
if (X < 4) { // Linke Feldgrenze erreicht?
Richtung = 13 - Richtung;
Punkte [1]++; // Punkt für RECHTS
if (X == 2) { X = 3;}
// Schläger getroffen ?
if (Y >= Schlaeger_Soll_Pos [0] && Y <= Schlaeger_Soll_Pos [0] + 5) {
Richtung = Richtung + Zufall_Richtung; // Richtung bestimmen
if (Richtung < 0) { Richtung = 2; } // Grenzwerte
if (Richtung > 6) { Richtung = 4; }
Punkte [1]--; // Kein Punktverlust
}
Punkte_Anzeige ();
}
}
}
// ************************************************************************************************
void Punkte_Anzeige () {
if (Spiel_laeuft) {
matrix.setCursor(11, 57); // Cursor neue Position
matrix.setTextColor(matrix.Color333(0,0,0)); // Textfarbe zum Löschen
String x1 = String (Punkte_Alt [0]);
String x2 = String (Punkte_Alt [1]);
matrix.println(x1 + " - " + x2); // Punkte Anzeige löschen
matrix.setCursor(11, 57); // Cursor neue Position
matrix.setTextColor(matrix.Color333(0,7,0)); // Textfarbe zum Löschen
x1 = String (Punkte [0]);
x2 = String (Punkte [1]);
matrix.println(x1 + " - " + x2); // Punkte aktuell anzeigen
} else {
Punkte [0] = Punkte [1] = 0;
}
}
// ************************************************************************************************
void Schlaeger_bewegen () {
for (int i=0; i<2; i++) { // X,Y Koordinaten
int PosX, PosY;
if (i == 0) { // Schläger LINKS
PosX = 2; // X-Koordinate Links
}
if (i == 1) { // Schläger RECHTS
PosX = 61; // X-Koordinate Rechts
}
if ( Schlaeger_Soll_Pos [i] != Pos_Alt [i]) {
PosY = Pos_Alt [i];
matrix.drawLine(PosX, PosY, PosX, PosY + 5, matrix.Color333(0, 0, 0)); // Löschen
PosY = Schlaeger_Soll_Pos [i];
matrix.drawLine(PosX, PosY, PosX, PosY + 5, matrix.Color333(7, 7, 7)); // Zeichnen
Pos_Alt [i] = Schlaeger_Soll_Pos [i]; // Alte Position merken
}
}
}
// ************************************************************************************************
void Analoges_Mittelwert () {
for (int i=0; i<9; i++) { // 10 Werte für Mittelwert
Analoge_Werte [0][i] = Analoge_Werte [0][i+1]; // Werte in Tabelle schieben
Analoge_Werte [1][i] = Analoge_Werte [1][i+1];
}
Analoge_Werte [0][9] = analogRead(A5); // Wert Poti Links auslesen
Analoge_Werte [1][9] = analogRead(A6); // Wert Poti Rechts auslesen
for (int i=0; i<10; i++) { // Mittelwerte ermitteln
Mittelwert [0] = Mittelwert [0] + Analoge_Werte [0][i];
Mittelwert [1] = Mittelwert [1] + Analoge_Werte [1][i];
}
Mittelwert [0] = Mittelwert [0] / 10;
Mittelwert [1] = Mittelwert [1] / 10;
Schlaeger_Soll_Pos [0] = ((Mittelwert [0] * 10) / 230) + 1; // Neue Schläger Position berechnen
Schlaeger_Soll_Pos [1] = ((Mittelwert [1] * 10) / 230) + 1;
}
// ************************************************************************************************
Für die Bewegung der Schläger sind zwei Potis zuständig. Mithilfe von diesen Potis können die Spieler ihre Schläger hin und her bewegen. Um
das zu realisieren, werden dauernd zwei analoge Eingänge ausgelesen und abhängig von dem ausgelesenen Wert die Y-Koordinate eines Schlägers bestimmt. Da sich
die Schläger jeweils auf einer Seite des Spielfeldes bewegen, bleiben die X-Koordinaten unverändert. Weil die anlogen Werte nicht immer stabil bleiben, wird
aus den letzten 10 analogen Werten ein Mittelwert errechnet. Für die Bewegung des Balles sind zwei weitere XY-Koordinaten notwendig, die dauernd neu berechnet
werden müssen. Um das zu vereinfachen, werden bestimmte Richtungen festgelegt, die der Ball annehmen kann. Diese Zuordnung wird auf der folgenden Abbildung
dargestellt:
Wenn sich z.B. der Ball gerade in Richtung 2 bewegt, werden seine Folgekoordinate nach der Formel X(next)=X+2, Y(next)=Y-1 berechnet. Die Berechnungen
finden in 50 ms-Takt, womit das Tempo der Bewegung bestimmt wird. Erreicht der Ball in dem Fall den oberen Rand des Spielfeldes, wird seine neue Richtung nach der
Formel „Richtung = 6 – Richtung“ berechnet. Seine neue Bewegungsrichtung hat folglich jetzt die Nummer 4 (Neue Richtung = 6 – 2 = 4). Die neuen Werte, die zu X- und
zu Y-Koordinaten addiert werden, sind in der Array-Variablen „X_Datensatz [14]“ und „Y_Datensatz [14]“ hinterlegt.
Um den Richtungswechsel unvorhersehbar zu machen, wird eine weitere Variable „Zufall_Richtung“ eingeführt. Hier handelt es sich um Werte zwischen -1 und 1,
die zu der errechneten Richtung addiert werden. Dieser unvorhersehbare Richtungswechsel kann nur dann stattfinden, wenn der Ball einen der Schläger trifft.
Zu kleinen Attraktionen des Spiels gehört die Änderung der Bewegungsgeschwindigkeit des Balles. Die Anzeige des Pixels, der den Ball symbolisiert, erfolgt stets
im festen Takt von 50 ms. Je nachdem, wie weit zwei Punkte auf der Matrix voneinander entfernt sind, ergibt sich daraus das aktuelle Bewegungstempo. Mit der
langsamsten Geschwindigkeit haben wir dann zu tun, wenn der Ball die Richtungen 3 oder 10 angenommen hat (Entfernung der Punkte = 1). Bei den Richtungen 1, 5, 8
und 12 bekommt man den Eindruck, dass der Ball schneller wird (Entfernung der Punkte = 1,41). Ganz schnelle Bewegung erfolgt in den Richtungen 0, 2, 4, 6, 7, 9,
11 und 12 (Entfernung der Punkte = 2,23).
Mit dem Taster S1 kann das Spiel gestartet bzw. unterbrochen werden.