Vor Kurzem hatte ich mir einen Ring aus 16 RGB-LEDs des Typs WS2812
bestellt (auch Neopixel genannt) und wollte damit ein wenig herumexperimentieren. Mir kam eine Idee einer
quasi 1-dimensionalen Adaption des Spiele-Klassikers "Pong".
So erfand ich kurzerhand ein paar neue Regeln, baute ein Prototyp auf und programmierte das Spiel in
kurzer Zeit. Und da das Spiel auf einem Ring angezeigt wird, nannte ich es einfach "Ring-Pong". Später
stellte ich fest, dass jemand auf YouTube schon ein ähnliches Projekt mit demselben Namen umgesetzt hatte,
aber meine Regeln weichen etwas von diesem Konzept ab.
Das Spielfeld ist ringförmig aufgebaut, d.h. das "Aus" (rot) des Spielfeldes befindet sich am unteren Ende
des Ringes (auf 6-Uhr-Stellung). Das Netz (gelb) befindet sich am oberen Ende des Ringes (12-Uhr-Stellung).
Die Spieler (blau) stehen zu Beginn jeweils links und rechts auf dem Ring.
Bei Spielbeginn hat einer der beiden Spieler Aufschlag (wird zufällig ermittelt) und der Ball (weiß) wird
langsam auf die andere Seite über das Netz zum Gegner geschlagen. Diese muss versuchen den Ball rechtzeitig
zu treffen, um ihn seinerseits zurückzuschlagen. Trifft der jeweilige Spieler den Ball genau 1 LED vor sich,
so wird der Ball einfach zurückgeschlagen. Trifft er ihn, wenn er auf demselben Feld wie der Spieler selbst
befindet, so muss der Spieler einen Schritt zurückgehen. Trifft er ihr zu früh - d.h. zwischen Gegner und
sich selbst, so rückt der Spieler näher dem Netz zu. Verpasst der Spieler den Ball ganz, so geht dieser
ins "Aus" und der Gegner gewinnt das Spiel.
Nach jedem erfolgreichen Schlag wird die Spielgeschwindigkeit ein wenig höher. Läuft ein Spieler ins Netz
oder ins "Aus", so gewinnt der Gegner.
Viel Spaß beim Nachbauen und Spielen!
Der Aufbau selbst ist relativ einfach: Es wird der LED-Ring richtig mit 5V und GND verkabelt und die DIN (data in)-Leitung bekommt noch einen Vorwiderstand. Der Kondensator ist als Sicherheit vor den LED-Ring geschaltet, um Stromspitzen abzufangen. Die beiden Mikrotaster werden mit je einem digitalen Eingang des Arduino verdrahtet und bekommt noch jeweils einen pulldown-Widerstand zur Entprellung.
#include <Adafruit_NeoPixel.h>
#define PIN_LED_RING 8
#define PIN_BUTTON_1 10
#define PIN_BUTTON_2 11
#define NUMPIXELS 16
#define GAME_INIT 0
#define GAME_RUNNING 1
#define GAME_ENDED 2
#define PLAYER_1 0
#define PLAYER_2 1
#define PLAYER_NONE 255
// LED positions of game objects
#define POS_NET 8
#define POS_OUT 0
#define POS_PLAYER1_START 4
#define POS_PLAYER2_START 12
// game speedup after player change in milliseconds
#define GAME_DELAY_SPEEDUP 100
// game delay in milliseconds
#define GAME_DELAY_INIT 1000
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN_LED_RING, NEO_GRB + NEO_KHZ800);
byte ledMapping[NUMPIXELS] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
byte playerPos[2], ballPos, gameState = GAME_ENDED, currentPlayer, wonPlayer, gameDelaySpeedup;
bool button1Pressed = false, button2Pressed = false;
byte button1State, button2State;
unsigned long previousMillis, currentMillis;
unsigned int gameDelay = GAME_DELAY_INIT;
void setup()
{
pixels.begin();
randomSeed(analogRead(0));
for(byte i=0; i<NUMPIXELS; i+=2) {
pixels.setPixelColor(i, pixels.Color(10, 10, 0));
pixels.show();
}
delay(400);
for(byte i=1; i<NUMPIXELS; i+=2) {
pixels.setPixelColor(i, pixels.Color(10, 10, 10));
pixels.show();
}
delay(400);
for(byte i=0; i<NUMPIXELS; i+=2) {
pixels.setPixelColor(i, pixels.Color(0, 0, 0));
pixels.show();
}
delay(400);
for(byte i=1; i<NUMPIXELS; i+=2) {
pixels.setPixelColor(i, pixels.Color(0, 0, 0));
pixels.show();
}
}
void loop()
{
button1State = digitalRead(PIN_BUTTON_1);
if (button1State == LOW) {
button1Pressed = false;
}
if (button1State == HIGH && !button1Pressed) {
button1Pressed = true;
buttonPressedPlayer1();
}
button2State = digitalRead(PIN_BUTTON_2);
if (button2State == LOW) {
button2Pressed = false;
}
if (button2State == HIGH && !button2Pressed) {
buttonPressedPlayer2();
}
if(gameState == GAME_RUNNING) {
drawObjects();
updateBall();
}
}
void initGame()
{
gameState = GAME_INIT;
gameDelay = GAME_DELAY_INIT;
if (random(0, 100) > 50) {
currentPlayer = PLAYER_2;
ballPos = POS_PLAYER1_START+1;
} else {
currentPlayer = PLAYER_1;
ballPos = POS_PLAYER2_START-1;
}
playerPos[PLAYER_1] = POS_PLAYER1_START;
playerPos[PLAYER_2] = POS_PLAYER2_START;
wonPlayer = PLAYER_NONE;
gameState = GAME_RUNNING;
gameDelaySpeedup = GAME_DELAY_SPEEDUP;
currentMillis = millis();
previousMillis = currentMillis;
for(byte i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(10, 10, 10));
delay(50);
pixels.show();
}
for(byte i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(0, 0, 0));
delay(50);
pixels.show();
}
}
void updateBall()
{
currentMillis = millis();
if (currentMillis - previousMillis >= gameDelay) {
previousMillis = currentMillis;
if (currentPlayer == PLAYER_1) {
ballPos--;
} else {
ballPos++;
}
if (ballPos > NUMPIXELS-1) {
ballPos = 0;
}
checkGameState();
}
}
void checkGameState()
{
if (currentPlayer == PLAYER_1 && ballPos == POS_OUT
|| playerPos[PLAYER_1] == POS_NET
|| playerPos[PLAYER_1] == POS_OUT) {
wonPlayer = PLAYER_2;
} else if (currentPlayer == PLAYER_2 && ballPos == POS_OUT
|| playerPos[PLAYER_2] == POS_NET
|| playerPos[PLAYER_2] == POS_OUT) {
wonPlayer = PLAYER_1;
}
if (wonPlayer != PLAYER_NONE) {
gameState = GAME_ENDED;
drawEndScreen(wonPlayer);
}
}
void buttonPressedPlayer1()
{
if (gameState == GAME_RUNNING) {
if (currentPlayer == PLAYER_1) {
if (ballPos == playerPos[PLAYER_1]) {
playerPos[PLAYER_1]--;
} else if (ballPos > playerPos[PLAYER_1]+1) {
playerPos[PLAYER_1] = ballPos-1;
}
playerPos[PLAYER_1] = max(0, min(POS_NET, playerPos[PLAYER_1]));
if (ballPos >= playerPos[PLAYER_1]) {
currentPlayer = PLAYER_2;
speedUpGame();
}
}
} else if (gameState == GAME_ENDED) {
initGame();
}
}
void buttonPressedPlayer2()
{
if (gameState == GAME_RUNNING) {
if (currentPlayer == PLAYER_2) {
if (ballPos == playerPos[PLAYER_2]) {
playerPos[PLAYER_2]++;
} else if (ballPos < playerPos[PLAYER_2]-1) {
playerPos[PLAYER_2] = ballPos+1;
}
playerPos[PLAYER_2] = max(POS_NET, playerPos[PLAYER_2]);
if (playerPos[PLAYER_2] > NUMPIXELS-1) {
playerPos[PLAYER_2] = 0;
}
if (playerPos[PLAYER_2] == 0 || ballPos <= playerPos[PLAYER_2]) {
currentPlayer = PLAYER_1;
speedUpGame();
}
}
} else if (gameState == GAME_ENDED) {
initGame();
}
}
void speedUpGame()
{
byte diff = (byte)gameDelaySpeedup * 0.1;
if (gameDelaySpeedup-diff > 0) {
gameDelaySpeedup -= diff;
}
gameDelay -= gameDelaySpeedup;
gameDelay = max(10, gameDelay);
}
void drawObjects()
{
for(byte i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(0, 0, 0));
}
// Net (top center)
pixels.setPixelColor(POS_NET, pixels.Color(10, 10, 0));
// Out (bottom center)
pixels.setPixelColor(POS_OUT, pixels.Color(10, 0, 0));
// Ball
pixels.setPixelColor(ballPos, pixels.Color(20, 20, 10));
// playerPositions
pixels.setPixelColor(playerPos[PLAYER_1], pixels.Color(0, 0, 20));
pixels.setPixelColor(playerPos[PLAYER_2], pixels.Color(0, 0, 20));
pixels.show();
}
void drawEndScreen(byte wonPlayer)
{
for(byte i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(2, 0, 0));
if (wonPlayer == PLAYER_1 && i<=POS_NET
|| wonPlayer == PLAYER_2 && i>POS_NET) {
pixels.setPixelColor(i, pixels.Color(0, 50, 0));
}
}
pixels.setPixelColor(POS_NET, pixels.Color(0, 0, 0));
pixels.setPixelColor(POS_OUT, pixels.Color(0, 0, 0));
pixels.show();
delay(3000);
}