Arduino “having11” Guy
Published © GPL3+

Handheld Two-Player Pong with NeoPixels

Play Pong with a friend without the need for a screen.

IntermediateFull instructions provided6 hours910
Handheld Two-Player Pong with NeoPixels

Things used in this project

Hardware components

Microchip ATmega8A
×1
WS2812b
×48
Capacitor 1000 µF
Capacitor 1000 µF
×1
Momentary Pushbutton Tall
×4
5v Regulator
×1
16MHz Crystal Oscillator
×1

Software apps and online services

Arduino IDE
Arduino IDE
VS Code
Microsoft VS Code
Atmel Studio 7
Microchip Atmel Studio 7

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Schematic

PCB

Code

Handheld Pong

C/C++
#include <Adafruit_NeoPixel.h>
#include <EEPROM.h>
#include "offset_table.h"

#ifdef __AVR__
 #include <avr/power.h>
#endif

#define PIXEL_NUM 48
#define WIDTH 8
#define HEIGHT 6

#define LED_PIN 4
#define WRITE_ORDER_DATA false

// 40 ms between frames
#define FRAME_DELAY 40
#define MOVE_DELAY 20

Adafruit_NeoPixel pixels(PIXEL_NUM, LED_PIN, NEO_GRB + NEO_KHZ800);

// Paddle 1 left, paddle 1 right, paddle 2 left, paddle 2 right
const uint8_t buttonPins[4] = {3, 1, 2, 0};

uint32_t lastFrameUpdate = 0, lastMoveUpdate = 0;

// Paddle 1, paddle 2, ball
const uint32_t colors[3] = {0x000010D0, 0x000D010, 0x00E5A500};

// Paddle 1 x/y, paddle 2 x/y, ball x/y
uint8_t piecePositions[6] = {WIDTH/2 - 1, 0, WIDTH/2 - 1, HEIGHT-1, WIDTH / 3, HEIGHT/3};
int8_t ballVector[2] = {1, 1};

uint8_t p1Wins = 0, p2Wins = 0;

void setup()
{
    pixels.begin();
    pixels.clear();
    pixels.setBrightness(50);
    if(WRITE_ORDER_DATA)
    {
        for(int i=0; i<PIXEL_NUM; i++)
        {
            EEPROM.write(i, offsets[i]);
            pixels.setPixelColor(EEPROM.read(i), 0x002050FF);
            pixels.show();
            delay(400);
        }
        // Program is done, ready to flash the game
        while(1);
    }
    for(int i=0; i<4; i++)
    {
        pinMode(buttonPins[i], INPUT_PULLUP);
    }
    lastFrameUpdate = millis();
    lastMoveUpdate = millis();
}

void loop()
{
    if(millis() - FRAME_DELAY >= lastFrameUpdate)
    {
        drawFrame();
        lastFrameUpdate = millis();
    }
    // Check button states
    if(millis() - MOVE_DELAY >= lastMoveUpdate)
    {
        if(digitalRead(buttonPins[0]))
        {
            if(piecePositions[0]>0) piecePositions[0]--;
        }
        else if(digitalRead(buttonPins[1]))
        {
            if(piecePositions[0]<WIDTH-2) piecePositions[0]++;
        }
        if(digitalRead(buttonPins[2]))
        {
            if(piecePositions[2]>0) piecePositions[2]--;
        }
        else if(digitalRead(buttonPins[3]))
        {
            if(piecePositions[2]<WIDTH-2) piecePositions[2]++;
        }
        lastMoveUpdate = millis();
    }
    if(checkCollisions()) drawScores();
}

void drawFrame()
{
    pixels.clear();
    // Draw paddles
    pixels.setPixelColor(EEPROM.read(XYtoLED(piecePositions[0], piecePositions[1])), colors[0]);
    pixels.setPixelColor(EEPROM.read(XYtoLED(piecePositions[0]+1, piecePositions[1])), colors[0]);
    pixels.setPixelColor(EEPROM.read(XYtoLED(piecePositions[2], piecePositions[4])), colors[1]);
    pixels.setPixelColor(EEPROM.read(XYtoLED(piecePositions[2]+1, piecePositions[4])), colors[1]);
    // Update ball position
    pixels.setPixelColor(EEPROM.read(XYtoLED(piecePositions[5], piecePositions[6])), colors[2]);
    pixels.show();
}

void resetGame()
{
    piecePositions[0] = WIDTH/2 - 1;
    piecePositions[1] = 0;
    piecePositions[2] = WIDTH/2 - 1;
    piecePositions[3] = HEIGHT-1;
    piecePositions[4] = WIDTH / 3;
    piecePositions[5] = HEIGHT/3;
    drawFrame();
    delay(1000);
}

bool checkCollisions()
{
    if(piecePositions[5] <= 0)
    {
        // If both p1 x and ball x are the same
        if(piecePositions[4] == piecePositions[0] || piecePositions[4] == piecePositions[0] + 1)
        {
            // Reflect ball
            ballVector[1] = -ballVector[1];
            return false;
        }
        else
        {
            // Player 1 has lost
            p2Wins++;
            return true;
        }
    }
    else if(piecePositions[5] >= HEIGHT-1)
    {
        if(piecePositions[4] == piecePositions[2] || piecePositions[4] == piecePositions[2] + 1)
        {
            ballVector[1] = -ballVector[1];
            return false;
        }
        else
        {
            // Player 2 has lost
            p1Wins++;
            return true;
        }
    }
    if(piecePositions[4] <= 0)
    {
        ballVector[0] = -ballVector[0];
        return false;
    }
    else
    {
        ballVector[0] = -ballVector[0];
        return false;
    }
    
}

void drawScores()
{
    for(int i = 0; i < p1Wins; i++)
    {
        pixels.setPixelColor(EEPROM.read(i), colors[0]);
    }
    for(int i = p1Wins; i < p2Wins + p1Wins; i++)
    {
        pixels.setPixelColor(EEPROM.read(i), colors[1]);
    }
    pixels.show();
    delay(3000);
    resetGame();
}

uint8_t XYtoLED(uint8_t x, uint8_t y)
{
    return (y * WIDTH) + x;
}

offset_table.h

C Header File
const uint8_t offsets[48] = {


};

Credits

Arduino “having11” Guy

Arduino “having11” Guy

81 projects • 538 followers
18 year-old IoT and embedded systems enthusiast. Also an intern at Hackster.io who loves working on projects and sharing knowledge.

Comments