Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
Make it for less
Published © GPL3+

Reaction Time Party Game

Two players face off to see who has the fastest reaction time. A shot glass full of something nasty is primed and ready to go to the loser.

IntermediateFull instructions provided8 hours58
Reaction Time Party Game

Things used in this project

Hardware components

ssd1306 (32x128)
×1
SG90 Micro-servo motor
SG90 Micro-servo motor
×1
Wemos D1 Mini
Espressif Wemos D1 Mini
×1
Gravity:Digital Push Button (Yellow)
DFRobot Gravity:Digital Push Button (Yellow)
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

arm

base

body

Schematics

Reaction circuit

Wiring Diagram for the project

Code

Logo.h

C Header File
Some extra code to hold images as byte arrays
#define LOGO_HEIGHT   32
#define LOGO_WIDTH    69

// 'MFLletter-09', 69x32px
const unsigned char logo_bmp [] PROGMEM = {
	0xfe, 0x03, 0xf8, 0x07, 0xff, 0xf0, 0x07, 0xe0, 0x00, 0xfe, 0x03, 0xf8, 0x07, 0xff, 0xf0, 0x07, 
	0xe0, 0x00, 0xff, 0x07, 0xf8, 0x07, 0xff, 0xf0, 0x07, 0xe0, 0x00, 0xff, 0x8f, 0xf8, 0x07, 0xff, 
	0xf0, 0x07, 0xe0, 0x00, 0xff, 0x8f, 0xf8, 0x07, 0xff, 0xf0, 0x07, 0xe0, 0x00, 0xff, 0xdf, 0xf8, 
	0x07, 0xff, 0xf0, 0x07, 0xe0, 0x00, 0xff, 0xff, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xe0, 0x00, 0xff, 
	0xff, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xe0, 0x00, 0xff, 0xff, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xe0, 
	0x00, 0xff, 0xff, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xe0, 0x00, 0xfd, 0xfd, 0xf8, 0x07, 0xe0, 0x00, 
	0x07, 0xe0, 0x00, 0xfc, 0xf9, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xe0, 0x00, 0xfc, 0xf9, 0xf8, 0x07, 
	0xe0, 0x00, 0x07, 0xe0, 0x00, 0xfc, 0x71, 0xf8, 0x07, 0xff, 0xf0, 0x07, 0xe0, 0x00, 0xfc, 0x21, 
	0xf8, 0x07, 0xff, 0xf0, 0x07, 0xe0, 0x00, 0xfc, 0x01, 0xf8, 0x07, 0xff, 0xf0, 0x07, 0xe0, 0x00, 
	0xfc, 0x01, 0xf8, 0x07, 0xff, 0xf0, 0x07, 0xe0, 0x00, 0xfc, 0x01, 0xf8, 0x07, 0xff, 0xf0, 0x07, 
	0xe0, 0x00, 0xfc, 0x01, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xe0, 0x00, 0xfc, 0x01, 0xf8, 0x07, 0xe0, 
	0x00, 0x07, 0xe0, 0x00, 0xfc, 0x01, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xe0, 0x00, 0xfc, 0x01, 0xf8, 
	0x07, 0xe0, 0x00, 0x07, 0xe0, 0x00, 0xfc, 0x01, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xe0, 0x00, 0xfc, 
	0x01, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xe0, 0x00, 0xfc, 0x01, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xe0, 
	0x00, 0xfc, 0x01, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xe0, 0x00, 0xfc, 0x01, 0xf8, 0x07, 0xe0, 0x00, 
	0x07, 0xff, 0xf8, 0xfc, 0x01, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xff, 0xf8, 0xfc, 0x01, 0xf8, 0x07, 
	0xe0, 0x00, 0x07, 0xff, 0xf8, 0xfc, 0x01, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xff, 0xf8, 0xfc, 0x01, 
	0xf8, 0x07, 0xe0, 0x00, 0x07, 0xff, 0xf8, 0xfc, 0x01, 0xf8, 0x07, 0xe0, 0x00, 0x07, 0xff, 0xf8
};

#define ARROW_HEIGHT   14
#define ARROW_WIDTH    40

// 'Arrow_right', 40x14px
const unsigned char arrow_right_bmp [] PROGMEM = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 
	0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 
	0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 
	0x80, 0x00, 0x00, 0x00, 0x00, 0x00
};

// 'Arrow_right', 40x14px
const unsigned char arrow_left_bmp [] PROGMEM = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x0f, 
	0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
	0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 
	0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

reaction_game.ino

C/C++
The main code that runs the game
//Imports
#include <Servo.h>
#include <Adafruit_SSD1306.h>
#include "logo.h"

//Pin assignments
#define servoPin D3
#define reactionLEDPin D4
#define button1Pin D6
#define button2Pin D5
#define displaySDAPin D2
#define displaySCLPin D3

#define DEBUG

//Constants
bool devSkips = true;
int arrowDrawOffset = 5;
const char * startGameText = " Hold both buttons to \n        start";
const int servoRange = 50;
//Globals
enum gameStates{
    NEEDS_RESET,
    PRE_GAME_IDLE,
    BUTTONS_HELD_START,
    GAME_IN_PROGRESS,
    GAME_OVER,
};

bool gameOverReleaseButton1, gameOverReleaseButton2;

gameStates gameState = gameStates::NEEDS_RESET;

bool player1Wins = false;

long randomWait, waitStart, gameStartTime = 0, button1TimeOffset = -8, button2TimeOffset = -12, fastTime;

int16_t boundingX, boundingY;
uint16_t boundingWidth, boundingHeight;

Servo armServo;

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

#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

//for logo movement
//x_pos, y_pos is top left corner
int x_pos = -LOGO_WIDTH, y_pos = 0, x_vel = 1, y_vel = 1;

//Set up logging (#ifdef DEBUG)
//Set pin modes
//Do intro animation
void setup(){
    #ifdef DEBUG
        Serial.begin(9600);
        Serial.println("program starting");
    #endif
    // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
    if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
        #ifdef DEBUG
            Serial.println(F("SSD1306 allocation failed"));
        #endif
        for(;;); // Don't proceed, loop forever
    }
    if(!devSkips){
        while(x_pos <= display.width()){
            display.clearDisplay();
            display.drawBitmap(
                x_pos,
                display.height()/2 - LOGO_HEIGHT/2,
                logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
            display.display();
            x_pos += x_vel;
            if(x_pos == display.width()/2 - LOGO_WIDTH/2){
                delay(1000);
            }
        }
    }
    armServo.attach(servoPin);
    armServo.write(90);
    pinMode(button1Pin,INPUT_PULLUP);
    pinMode(button2Pin,INPUT_PULLUP);
    pinMode(reactionLEDPin,OUTPUT);
}

//Wait for both players to press buttons
//Move servo to middle
//Do loop animation with LED on
//When both players release turn off LED and stop animation
//Random wait to turn on LED
//Record reaction time for both players
//Display lowest reaction time (or 9.9 if overflow)
//Wait random time
//Do random servo animation
//Move servo to losers side
//Reset
void loop(){
    if(gameState == gameStates::NEEDS_RESET){
        drawStartIdle();
        digitalWrite(reactionLEDPin,LOW);
        if(armServo.read() > 90){
            for(int i = 0; i <= servoRange; i++){
                armServo.write(servoRange + 90 -i);
                delay(10);
            }
        }else if (armServo.read() < 90){
            for(int i = 0; i <= servoRange; i++){
                armServo.write(90 - servoRange + i);
                delay(10);
            }
        }
        gameStartTime = 0;
        gameState = gameStates::PRE_GAME_IDLE;
    }
    if(gameState == gameStates::PRE_GAME_IDLE){
        if(!digitalRead(button1Pin) && !digitalRead(button2Pin)){
            drawGameStart();
            gameState = gameStates::BUTTONS_HELD_START;
        }
    }
    if(gameState == gameStates::BUTTONS_HELD_START){
        if(digitalRead(button1Pin) && digitalRead(button2Pin)){
            doCountDown();
            randomWait = random(1e+6,8e+6);
            waitStart = micros();
            gameState = gameStates::GAME_IN_PROGRESS;
        }
    }
    if(gameState == gameStates::GAME_IN_PROGRESS){
        bool waitDone = false;
        long button1Time = 0, button2Time = 0;
        if(micros() - waitStart > randomWait && gameStartTime == 0){
            digitalWrite(reactionLEDPin,HIGH);
            gameStartTime = micros();
        }
        if(gameStartTime != 0){
            waitDone = true;
        }
        if(!digitalRead(button1Pin)){
            button1Time = micros();
        }
        if(!digitalRead(button2Pin)){
            button2Time = micros();
        }
        if(button1Time != 0){
            if(waitDone){
                //player 1 wins
                button1Time += button1TimeOffset;
                button1Time = button1Time - gameStartTime;
                player1Wins = true;
                fastTime = button1Time;
            }else{
                //player 2 wins
                player1Wins = false;
                fastTime = -1;
            }
            gameOverReleaseButton1 = false;
            gameOverReleaseButton2 = false;
            gameState = gameStates::GAME_OVER;
            return;
        }
        if(button2Time != 0){
            if(waitDone){
                //player 2 wins
                button2Time += button2TimeOffset;
                button2Time = button2Time - gameStartTime;  
                player1Wins = false;
                fastTime = button2Time;
            }else{
                //player 1 wins
                player1Wins = true;
                fastTime = -1;
            }
            gameOverReleaseButton1 = false;
            gameOverReleaseButton2 = false;
            gameState = gameStates::GAME_OVER;
            return;
        }
    }
    if(gameState == gameStates::GAME_OVER){
        digitalWrite(reactionLEDPin,LOW);
        drawWinnerAndTime();
       
        if(armServo.read() == 90){
            delay(1000);
            for(int i = 0; i <= servoRange; i++){
                if(player1Wins){
                    armServo.write(90-i);
                }else{
                    armServo.write(90+i);
                }
                delay(10);
            }
        }
        if(digitalRead(button1Pin)){
            gameOverReleaseButton1 = true;
        }
        if(digitalRead(button2Pin)){
            gameOverReleaseButton2 = true;
        }
        if(gameOverReleaseButton1 && gameOverReleaseButton2){
            if(!digitalRead(button1Pin) || !digitalRead(button2Pin)){
                gameState = gameStates::NEEDS_RESET;
            }
        }
    }
}

void drawStartIdle(){
    display.clearDisplay();
    display.setTextSize(1);             // Normal 1:1 pixel scale
    display.setTextColor(SSD1306_WHITE);        // Draw white text
    display.setCursor(0,0);             // Start at top-left corner
    
    display.println(startGameText);
    display.drawBitmap(
        display.width() - ARROW_WIDTH,
        display.height()/2 - ARROW_HEIGHT/2 + arrowDrawOffset,
        arrow_right_bmp, ARROW_WIDTH, ARROW_HEIGHT, 1);
    display.drawBitmap(
        0,
        display.height()/2 - ARROW_HEIGHT/2 + arrowDrawOffset,
        arrow_left_bmp, ARROW_WIDTH, ARROW_HEIGHT, 1);
    display.display();    
}

void drawGameStart(){
    display.clearDisplay();
    display.setTextSize(2);             // Normal 1:1 pixel scale
    display.setTextColor(SSD1306_WHITE);        // Draw white text
    display.setCursor(0,0);             // Start at top-left corner
    
    display.println("release to continue");
    display.display(); 
}

void doCountDown(){
    display.clearDisplay();
    display.setTextSize(2);             // Normal 1:1 pixel scale
    display.setTextColor(SSD1306_WHITE);        // Draw white text
    display.setCursor(0,0);             // Start at top-left corner
    
    display.println("get ready");
    display.display(); 
    // delay(500);
}

void drawWinnerAndTime(){
    display.clearDisplay();
    display.setTextSize(2);             // Normal 1:1 pixel scale
    display.setTextColor(SSD1306_WHITE);        // Draw white text
    display.setCursor(0,0);             // Start at top-left corner
    
    // display.print("winner was ");
    // display.println(player1Wins ? "Player 1" : "player 2");
    if(fastTime/1000 != 0){
        display.println(fastTime/1000);
        display.println("Millisecond");
    }else{
        display.println("Too early");
    }
    display.display(); 
}

Credits

Make it for less
3 projects • 3 followers
Contact

Comments

Please log in or sign up to comment.