Skip to content

Instantly share code, notes, and snippets.

@hrydgard
Created October 5, 2017 14:44
Show Gist options
  • Save hrydgard/c1e272ce5dc663fca00c1a2bb3423785 to your computer and use it in GitHub Desktop.
Save hrydgard/c1e272ce5dc663fca00c1a2bb3423785 to your computer and use it in GitHub Desktop.
Simple Arduino timing game
// 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