Created
October 5, 2017 14:44
-
-
Save hrydgard/c1e272ce5dc663fca00c1a2bb3423785 to your computer and use it in GitHub Desktop.
Simple Arduino timing game
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Simple Arduino timing game. | |
// Made for Nano but probably works on Uno too. | |
// by Henrik Rydgård | |
// hrydgard@gmail.com | |
// | |
// Connect: | |
// * a 0.96" no-name I2C OLED screen to pins SDA=A4,SCL=A5 | |
// * a button to pin 4 and ground (we use internal pullup to avoid adding a resistor) | |
// * a 8-LED RGB strip to pin 2 | |
// and that's pretty much it. | |
#include "FastLED.h" | |
#include <U8g2lib.h> | |
#define NUM_LEDS 8 | |
CRGB leds[NUM_LEDS]; | |
U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8; // (7) | |
int lednum = 0; | |
unsigned long lastTime = 0; | |
unsigned long lastStateChange; | |
unsigned long gameStart; | |
bool expectLateClick; | |
enum State { | |
STATE_IDLE, | |
STATE_GAME_SUSPENSE, | |
STATE_GAME_WAIT, | |
STATE_GAME_REACT, | |
STATE_GAME_WIN, | |
STATE_GAME_LOSE, | |
}; | |
State state = STATE_IDLE; | |
// in milliseconds | |
int suspenseTime = 1000; | |
int waitTime = 250; | |
uint16_t thishue; | |
const uint8_t deltahue = 10; | |
const char *judge(int elapsed) { | |
if (elapsed < 100) return "INHUMAN!"; | |
if (elapsed < 140) return "STUPENDOUS!"; | |
if (elapsed < 150) return "INCREDIBLE!"; | |
if (elapsed < 160) return "AMAZING!"; | |
if (elapsed < 170) return "FANTASTIC!"; | |
if (elapsed < 180) return "EXCELLENT!"; | |
if (elapsed < 190) return "GREAT!"; | |
if (elapsed < 200) return "VERY GOOD!"; | |
if (elapsed < 215) return "GOOD!"; | |
if (elapsed < 230) return "NOT BAD!"; | |
if (elapsed < 250) return "OKAY."; | |
if (elapsed < 300) return "SNAIL"; | |
if (elapsed < 500) return "SLOTH"; | |
if (elapsed < 700) return "WAIT FOR GODOT"; | |
return "DO YOU EVEN LIFT"; | |
} | |
void changeState(State newState) { | |
state = newState; | |
lastStateChange = millis(); | |
} | |
void drawTitle() { | |
u8x8.clearDisplay(); | |
u8x8.drawString(5, 0, "REACT!"); | |
u8x8.drawString(2, 2, "PRESS BUTTON"); | |
u8x8.drawString(4, 3, "TO START"); | |
u8x8.drawString(0, 5, " BY"); | |
u8x8.drawString(0, 6, " HENRIK RYDGARD"); | |
} | |
void setup() { | |
// put your setup code here, to run once: | |
pinMode(LED_BUILTIN, OUTPUT); | |
FastLED.addLeds<NEOPIXEL, 2>(leds, NUM_LEDS); | |
pinMode(4, INPUT_PULLUP); | |
set_max_power_in_volts_and_milliamps(5, 300); | |
lastStateChange = millis(); | |
u8x8.begin(); | |
u8x8.setFont(u8x8_font_chroma48medium8_r); | |
drawTitle(); | |
} | |
void loop() { | |
static int lastButton = 0; | |
int button = !digitalRead(4); | |
int buttonDown = button & !lastButton; | |
char buffer[32]; | |
lastButton = button; | |
// put your main code here, to run repeatedly: | |
for (int i = 0; i < 8; i++) { | |
leds[i] = CRGB::Black; | |
} | |
switch (state) { | |
case STATE_IDLE: | |
{ | |
int value = (thishue & 0xFF) >> 2; | |
int antiValue = value ^ 0x3f; | |
value = value * value >> 8; | |
antiValue = antiValue * antiValue >> 8; | |
leds[(thishue >> 8) & 7] = CRGB(antiValue, antiValue, antiValue); | |
leds[((thishue >> 8) + 1) & 7] = CRGB(value, value, value); | |
thishue += 2; | |
if (buttonDown) { | |
suspenseTime = rand() % 4000 + 800; | |
changeState(STATE_GAME_SUSPENSE); | |
u8x8.clearDisplay(); | |
u8x8.drawString(6, 2, "GO!"); | |
} | |
expectLateClick = false; | |
break; | |
} | |
case STATE_GAME_SUSPENSE: | |
{ | |
fill_rainbow(leds, NUM_LEDS, thishue >> 8, deltahue); | |
fadeToBlackBy(leds, 8, 190); | |
thishue += 50; // let it wrap | |
if (millis() > lastStateChange + suspenseTime) { | |
changeState(STATE_GAME_WAIT); | |
gameStart = millis(); | |
} | |
if (buttonDown) { | |
changeState(STATE_GAME_LOSE); | |
u8x8.clearDisplay(); | |
u8x8.drawString(0, 2, "TOO EARLY"); | |
} | |
break; | |
} | |
case STATE_GAME_WAIT: | |
{ | |
for (int i = 0; i < 8; i++) { | |
leds[i].r = 100; | |
leds[i].g = 100; | |
leds[i].b = 100; | |
} | |
int16_t elapsed = millis() - gameStart; | |
if (elapsed < waitTime) { | |
if (buttonDown) { | |
snprintf(buffer, sizeof(buffer), "%d ms:", elapsed); | |
changeState(STATE_GAME_WIN); | |
u8x8.clearDisplay(); | |
u8x8.drawString(0, 2, buffer); | |
u8x8.drawString(0, 3, judge(elapsed)); | |
} | |
} else { | |
changeState(STATE_GAME_LOSE); | |
u8x8.clearDisplay(); | |
u8x8.drawString(0, 2, "TOO LATE"); | |
expectLateClick = true; | |
} | |
break; | |
} | |
case STATE_GAME_WIN: | |
{ | |
for (int i = 2; i < 6; i++) { | |
leds[i].g = 50; | |
} | |
if (buttonDown || (millis() > lastStateChange + 5000)) { | |
changeState(STATE_IDLE); | |
drawTitle(); | |
} | |
break; | |
} | |
case STATE_GAME_LOSE: | |
{ | |
for (int i = 2; i < 6; i++) { | |
leds[i].r = 50; | |
} | |
// Don't allow restarting by button press until some time has passed, | |
// to avoid near-misses resetting. | |
if (millis() > lastStateChange + 1000) { | |
if (buttonDown || (millis() > lastStateChange + 5000)) { | |
changeState(STATE_IDLE); | |
drawTitle(); | |
} | |
} else { | |
int elapsed = millis() - gameStart; | |
if (buttonDown && expectLateClick) { | |
snprintf(buffer, sizeof(buffer), "%d ms:", elapsed); | |
u8x8.drawString(0, 3, buffer); | |
u8x8.drawString(0, 4, judge(elapsed)); | |
expectLateClick = false; | |
} | |
} | |
break; | |
} | |
default: | |
; | |
} | |
fadeToBlackBy(leds, 8, 200); | |
FastLED.show(); | |
delay(1); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment