Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
typalowski
Published © GPL3+

Automated Beer Pong Game - Moving Cups!

Drinking game too easy? Here's a way to increase the difficulty to your liking!

IntermediateShowcase (no instructions)2,429
Automated Beer Pong Game - Moving Cups!

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
Rotary Encoder with Push-Button
Rotary Encoder with Push-Button
×1
Limit Switch, 5 A
Limit Switch, 5 A
×1
OLED Display, Blue on Black
OLED Display, Blue on Black
×1
Stepper Motor, Mini Step
Stepper Motor, Mini Step
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Custom parts and enclosures

Magnet Shuttle

Magnet shuttle which fits ontop of AL202 Slider

Stepper Motor Holder

Holds stepper motor and AL2020 rail.

Limit Switch Holder

Holds limit switch and AL2020 Rail

Schematics

Wiring Schedule for Components

Works with Arduino Uno and Nano. Needs 12V supply and 5V to power motor driver and Arduino, respectively.

Code

Code for Automated Beer Pong Game

C/C++
This is for the game, some adjustments might have to be made if using a different motor driver (TB6600) or motor with different degrees of steps.
/*
 * Code by Ty Palowski for Automated Beerpong Game
 * https://youtube.com/c/typalowski
 */


#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ClickEncoder.h>
#include <TimerOne.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// defines pins numbers
const int dirPin = 2; 
const int stepPin = 4; 
const int enPin = 5;

int menuitem = 1;
int frame = 1;
int page = 1;
int lastMenuItem = 1;

String menuItem1 = "Motor: OFF";
String menuItem2 = "Difficulty";
String menuItem3 = "Mode";
String menuItem4 = "Reset";

boolean motor = true;
int stepdelay;

int maxDelay = 240;
int diffDelta = 22;

String difficulty[10] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" };
int selectedDifficulty = 0;

String mode[2] = { "EASY", "HARD"};
int selectedMode = 1;

boolean up = false;
boolean down = false;
boolean middle = false;

ClickEncoder *encoder;
int16_t last, value;

// '85062033-beer-pong-or-beirut-drinking-game-with-cups-with-ball-line-art-vector-icon-for-apps-and-websites-', 128x32px
const unsigned char myBitmap [] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x7c, 0x7c, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x83, 0x83, 0x83, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x98, 0x38, 0x38, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0xff, 0xff, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0xc3, 0xc3, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x83, 0x83, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0xff, 0xff, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x98, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x9c, 0x71, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x9f, 0xf1, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x90, 0x13, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x98, 0x13, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc8, 0x23, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

#define home_switch 9 // Pin 9 connected to Home Switch (MicroSwitch)

int direction;    // Variable to set Rotation (CW-CCW) of the motor
int steps;        // Used to set HOME position after Homing is completed

int fullDist = 32766;
int halfDist = fullDist/2;


void setup() {
  Serial.begin(9600);

  
  // Sets the two pins as Outputs
  pinMode(stepPin,OUTPUT); 
  pinMode(dirPin,OUTPUT);

  pinMode(enPin,OUTPUT);
  digitalWrite(enPin,LOW);


  pinMode(7,OUTPUT);

  
  encoder = new ClickEncoder(A1, A0, A2);
  encoder->setAccelerationEnabled(false);

  display.begin();      
  display.clearDisplay(); 

  Timer1.initialize(1000);
  Timer1.attachInterrupt(timerIsr); 
  
  last = encoder->getValue();

  display.setRotation (2);

  display.drawBitmap(0, 0, myBitmap, 128, 64, WHITE); // display.drawBitmap(x position, y position, bitmap data, bitmap width, bitmap height, color)

  // Show the display buffer on the screen. 
  display.display();


// Start Homing procedure of Stepper Motor at startup
Serial.println("Homing...");
  while (digitalRead(home_switch)) {  // Do this until the switch is activated   
    digitalWrite(dirPin, HIGH);      // (HIGH = anti-clockwise / LOW = clockwise)
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(75);                       // Delay to slow down speed of Stepper
    digitalWrite(stepPin, LOW);
    delayMicroseconds(75);   
}

  while (!digitalRead(home_switch)) { // Do this until the switch is not activated
    digitalWrite(dirPin, LOW); 
    digitalWrite(stepPin, HIGH);
    delay(5);                       // More delay to slow even more while moving away from switch
    digitalWrite(stepPin, LOW);
    delay(5);
  }

  steps=0;  // Reset position variable to zero

    digitalWrite(dirPin,LOW); // Enables the motor to move in a particular direction
  // Makes 200 pulses for making one full cycle rotation
  for(int x = 0; x < halfDist+400; x++) {
    digitalWrite(stepPin,HIGH); 
    delayMicroseconds(50); 
    digitalWrite(stepPin,LOW); 
    delayMicroseconds(50); 
    }
   Serial.println("Ready!");
  delay(1000);

}

void loop() {

stepdelay = maxDelay - selectedDifficulty*diffDelta;

      if (motor == true) 
      {
        turnMotorOff();
        }
      else
      { 
        turnMotorOn();
       }

  drawMenu();

  readRotaryEncoder();

   ClickEncoder::Button b = encoder->getButton();
   if (b != ClickEncoder::Open) {
   switch (b) {
      case ClickEncoder::Clicked:
         middle=true;
        break;
    }
  }    
  
  if (up && page == 1 ) //We have turned the Rotary Encoder CounterClockwise
  {
    up = false;
    if(menuitem==1 && frame ==2)
    {
      frame--;
    }
     if(menuitem==2 && frame ==3)
    {
      frame--;
    }
         if(menuitem==3 && frame ==4)
    {
      frame--;
    }
    
    lastMenuItem = menuitem;
    menuitem--;
    if (menuitem==0)
    {
      menuitem=1;
    }
  } 
  else if (up && page == 2 && menuitem==2 ) 
  {
    up = false;
    selectedDifficulty--;
    if(selectedDifficulty == -1)
    {
      selectedDifficulty = 9;
    }
  }
  else if (up && page == 2 && menuitem==4 ) {
    up = false;
  }
   else if (up && page == 2 && menuitem==3 ) {
    up = false;
    selectedMode--;
    if(selectedMode == -1)
    {
      selectedMode = 1;
    }
  }
  
  if (down && page == 1) //We have turned the Rotary Encoder Clockwise
  {

    down = false;
    if(menuitem==2 && lastMenuItem == 1)
    {
      frame ++;
    }
    else  if(menuitem==3 && lastMenuItem == 2)
    {
      frame ++;
    }
    else  if(menuitem==4 && lastMenuItem == 3 && frame!=3)
    {
      frame ++;
    }
    lastMenuItem = menuitem;
    menuitem++;  
    if (menuitem==5) 
    {
      menuitem--;
    }
  }
   else if (down && page == 2 && menuitem==2 ) {
    down = false;
    selectedDifficulty++;
    if(selectedDifficulty == 10)
    {
      selectedDifficulty = 0;
    }
  }
  else if (down && page == 2 && menuitem==3 ) {
    down = false;
    selectedMode++;
    if(selectedMode == 2)
    {
      selectedMode = 0;
    }
  }
  
  if (middle) //Middle Button is Pressed
  {
    middle = false;
   
    if (page == 1 && menuitem==1) // Motor Control 
    {
      if (motor) 
      {
        menuItem1 = "Motor: ON";
        motor = false;
        turnMotorOff();
        //Left to Center
    digitalWrite(dirPin,LOW); // Enables the motor to move in a particular direction
  for(int x = 0; x < halfDist; x++) {
    digitalWrite(stepPin,HIGH); 
    delayMicroseconds(stepdelay); 
    digitalWrite(stepPin,LOW); 
    delayMicroseconds(stepdelay); 
    }
        }
      else 
      { 
        menuItem1 = "Motor: OFF";
        motor = true;
        turnMotorOn();
        //Center to Right
    digitalWrite(dirPin,HIGH); // Enables the motor to move in a particular direction
  for(int x = 0; x < halfDist; x++) {
    digitalWrite(stepPin,HIGH); 
    delayMicroseconds(stepdelay); 
    digitalWrite(stepPin,LOW); 
    delayMicroseconds(stepdelay); 
    }
       }
    }

    if(page == 1 && menuitem ==4)// Reset
    {
      resetDefaults();
    }
    else if (page == 1 && menuitem==2) {
      page=2;
     }
    else if (page == 1 && menuitem==3) {
      page=2;
     }
    else if (page == 2) 
     {
      page=1; 
     }
   }   
  }
  
  void drawMenu()
  {
    
  if (page==1) 
  {    
    display.setTextSize(1);
    display.clearDisplay();
    display.setTextColor(WHITE, BLACK);
    display.setCursor(15, 0);
    display.print("MAIN MENU");
    display.drawFastHLine(0,10,128,WHITE);

    if(menuitem==1 && frame ==1)
    {   
      displayMenuItem(menuItem1, 15,true); //FRAME 1
      displayMenuItem(menuItem2, 25,false);
    }
    else if(menuitem == 2 && frame == 1)
    {
      displayMenuItem(menuItem1, 15,false);
      displayMenuItem(menuItem2, 25,true);
    }
    else if(menuitem == 3 && frame == 2)
    {
      displayMenuItem(menuItem2, 15,false); //FRAME 2
      displayMenuItem(menuItem3, 25,true);
    }

      else if(menuitem == 2 && frame == 2)
    {
      displayMenuItem(menuItem2, 15,true);
      displayMenuItem(menuItem3, 25,false);
    }
    else if(menuitem == 4 && frame == 3)
    {
      displayMenuItem(menuItem3, 15,false); //FRAME 3
      displayMenuItem(menuItem4, 25,true);
    }
    
    else if(menuitem == 3 && frame == 3)
    {
      displayMenuItem(menuItem3, 15,true);
      displayMenuItem(menuItem4, 25,false);
    }
    else if(menuitem == 2 && frame == 3)
    {
      displayMenuItem(menuItem2, 15,true);  //FRAME 2
      displayMenuItem(menuItem3, 25,false);
    }   
    else if(menuitem == 1 && frame == 2)
    {
      displayMenuItem(menuItem1, 15,true);  //FRAME 1
      displayMenuItem(menuItem2, 25,false);
    }   
    display.display();
  }
  else if (page==2 && menuitem == 2) 
  {
   displayStringMenuPage(menuItem2, difficulty[selectedDifficulty]);
  }
   else if (page==2 && menuitem == 3) 
  {
   displayStringMenuPage(menuItem3, mode[selectedMode]);
  }  
  }


  void resetDefaults()
  {
    selectedDifficulty = 0;
    selectedMode = 0;
    motor = true;
    menuItem1 = "Motor: OFF";// Start Homing procedure of Stepper Motor at startup

  while (digitalRead(home_switch)) {  // Do this until the switch is activated   
    digitalWrite(dirPin, HIGH);      // (HIGH = anti-clockwise / LOW = clockwise)
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(75);                      // Delay to slow down speed of Stepper
    digitalWrite(stepPin, LOW);
    delayMicroseconds(75);   
}

  while (!digitalRead(home_switch)) { // Do this until the switch is not activated
    digitalWrite(dirPin, LOW); 
    digitalWrite(stepPin, HIGH);
    delay(5);                       // More delay to slow even more while moving away from switch
    digitalWrite(stepPin, LOW);
    delay(5);
  }

  steps=0;  // Reset position variable to zero

    digitalWrite(dirPin,LOW); // Enables the motor to move in a particular direction
  for(int x = 0; x < halfDist+400; x++) {
    digitalWrite(stepPin,HIGH); 
    delayMicroseconds(50); 
    digitalWrite(stepPin,LOW); 
    delayMicroseconds(50); 
    }
   }

  void setstepdelay()
  {
  //  display.setContrast(contrast);
    display.display();
  }

  void turnMotorOn()
  {
//Left limit to Right Limit
    digitalWrite(dirPin,HIGH); 
  for(int x = 0; x < fullDist; x++) {
    digitalWrite(stepPin,HIGH); 
    delayMicroseconds(stepdelay); 
    digitalWrite(stepPin,LOW); 
    delayMicroseconds(stepdelay); 
    }
//Right limit to Left
    digitalWrite(dirPin,LOW); 
  for(int x = 0; x < fullDist; x++) {
    digitalWrite(stepPin,HIGH); 
    delayMicroseconds(stepdelay); 
    digitalWrite(stepPin,LOW); 
    delayMicroseconds(stepdelay); 
    }
  }

    void turnMotorOff()
  {
    digitalWrite(7,HIGH);
  }

  void timerIsr() {
  encoder->service();
}

void displayIntMenuPage(String menuItem, int value)
{
    display.setTextSize(1);
    display.clearDisplay();
    display.setTextColor(WHITE, BLACK);
    display.setCursor(10, 0);
    display.print(menuItem);
    display.drawFastHLine(0,10,80,WHITE);
    display.setCursor(5, 15);
    display.print("Value");
    display.setTextSize(2);
    display.setCursor(80, 10);
    display.print(value);
    display.setTextSize(2);
    display.display();
}

void displayStringMenuPage(String menuItem, String value)
{
    display.setTextSize(2);
    display.clearDisplay();
    display.setTextColor(WHITE, BLACK);
    display.setCursor(0, 0);
    display.print(menuItem);
    display.setCursor(50, 15);
    display.print(value);
    display.setTextSize(2);
    display.display();
}

void displayMenuItem(String item, int position, boolean selected)
{
    if(selected)
    {
      display.setTextColor(BLACK, WHITE);
    }else
    {
      display.setTextColor(WHITE, BLACK);
    }
    display.setCursor(0, position);
    display.print(">"+item);
}

void readRotaryEncoder()
{
  value += encoder->getValue();
  
  if (value/2 > last) {
    last = value/2;
    down = true;
    delay(150);
  }else   if (value/2 < last) {
    last = value/2;
    up = true;
    delay(150);
  }
}

Credits

typalowski
1 project • 23 followers

Comments