"Ring-Pong"

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 einen Prototypen 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, das jemand auf YouTube schon ein ähnliches Projekt mit demselben Namen umgesetzt hatte, aber meine Regeln weichen etwas von diesem Konzept ab.

Video: Live-Demonstration des Spiels (noch mit zwei Steckbrettern)

Funktionsweise des Spiels

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!

Verwendete Bauteile

Aufbau

Aufbau der Schaltung
Abb.: Aufbau der Schaltung

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.

Aufbau am Breadboard
Abb.: Aufbau am Breadboard

Sketch

#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);
}

Weitere Ideen

zurück