Hardware components | ||||||
| × | 1 | ||||
![]() |
| × | 1 | |||
| × | 1 | ||||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
![]() |
| |||||
Hand tools and fabrication machines | ||||||
![]() |
| |||||
![]() |
|
I created this project for my youtube channel. I wanted to make a fun and simple game that anyone could pick up and play. I settled on creating a reaction time game. The display at the bottom walks players through everything and the only setup needed is putting something nasty in the shot glass. A servo automatically swings the shot glass to the loser of the game so there is no confusion about who has to take the shot!
#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
};
//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();
}
Comments
Please log in or sign up to comment.