// Stranger things with magic 8 ball style question answering
//
// 2016, Jack Stalnaker
// Shift Register definitions
// -------------------------------------
// Pin connected to ST_CP of 74HC595
int latchPin = 8;
// Pin connected to SH_CP of 74HC595
int clockPin = 12;
// Pin connected to DS of 74HC595
int dataPin = 11;
// Pin connected to button
int buttonPin = 2;
// Multiplexing 3 rows X 9 cols of LEDS:
// 1 2 3 4 5 6 7 8 9 |
// ------------------|
// A B C D E F G H | A
// I J K L M N O P Q | B
// R S T U V W X Y Z | C
// rowcol
// ABC12345 6789xxxx
const byte A[2] = {0b10001111, 0b11110000};
const byte B[2] = {0b10010111, 0b11110000};
const byte C[2] = {0b10011011, 0b11110000};
const byte D[2] = {0b10011101, 0b11110000};
const byte E[2] = {0b10011110, 0b11110000};
const byte F[2] = {0b10011111, 0b01110000};
const byte G[2] = {0b10011111, 0b10110000};
const byte H[2] = {0b10011111, 0b11010000};
const byte I[2] = {0b01001111, 0b11110000};
const byte J[2] = {0b01010111, 0b11110000};
const byte K[2] = {0b01011011, 0b11110000};
const byte L[2] = {0b01011101, 0b11110000};
const byte M[2] = {0b01011110, 0b11110000};
const byte N[2] = {0b01011111, 0b01110000};
const byte O[2] = {0b01011111, 0b10110000};
const byte P[2] = {0b01011111, 0b11010000};
const byte Q[2] = {0b01011111, 0b11100000};
const byte R[2] = {0b00101111, 0b11110000};
const byte S[2] = {0b00110111, 0b11110000};
const byte T[2] = {0b00111011, 0b11110000};
const byte U[2] = {0b00111101, 0b11110000};
const byte V[2] = {0b00111110, 0b11110000};
const byte W[2] = {0b00111111, 0b01110000};
const byte X[2] = {0b00111111, 0b10110000};
const byte Y[2] = {0b00111111, 0b11010000};
const byte Z[2] = {0b00111111, 0b11100000};
const byte blank[2] = {0b00000000, 0b00000000};
const byte* alphabet[26] = {A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z};
const byte* runMsg[3] = {R, U, N};
const byte* noMsg[3] = {N, O};
const byte* yesMsg[3] = {Y, E, S};
const byte* maybeMsg[5] = {M, A, Y, B, E};
const byte* helpMsg[4] = {H, E, L, P};
const byte* rightHereMsg[9] = {R, I, G, H, T, H, E, R, E};
const byte* willByersMsg[9] = {W, I, L, L, B, Y, E, R, S};
const byte* demogorgonMsg[10] = {D, E, M, O, G, O, R, G, O, N};
const byte* randomMsg[25];
// Timing
unsigned long currentMillis;
int currentMsgIdx = 0;
// Magic 8 ball variables
volatile bool needMagic8BallAnswer = false;
// Send the message out to the shift registers
void send_letter(const byte* letter, int delayTime = 0) {
// take the latchPin low so
// the LEDs don't change while you're sending in bits:
digitalWrite(latchPin, LOW);
// shift out the bits:
shiftOut(dataPin, clockPin, MSBFIRST, letter[0]);
shiftOut(dataPin, clockPin, MSBFIRST, letter[1]);
//take the latch pin high so the LEDs will light up:
digitalWrite(latchPin, HIGH);
// delay if needed
delay(delayTime);
}
// Message script
class ScriptedMessage {
public:
ScriptedMessage(const byte** messageIn, int lenIn, int timeAfterMsgIn, int ltrDurationIn = 1000)
: message(messageIn), len(lenIn), timeAfterMsg(timeAfterMsgIn), ltrDuration(ltrDurationIn), currentLtrIdx(0), previousLtrMillis(millis()), msgDone(false)
{}
// Send the next letter in the message out
void send_next_letter() {
if (currentMillis - previousLtrMillis >= ltrDuration) {
if (!msgDone) {
if (currentLtrIdx == len - 1) {
msgDone = true;
doneMillis = currentMillis;
}
send_letter(message[currentLtrIdx]);
currentLtrIdx = (currentLtrIdx + 1) % len;
previousLtrMillis = currentMillis;
} else {
send_letter(blank);
}
}
}
// is this message done?
bool is_done() const {
if (msgDone && (currentMillis - doneMillis > timeAfterMsg)) {
return true;
}
else return false;
}
// reset the message
void reset() {
msgDone = false;
currentLtrIdx = 0;
}
private:
const byte** message;
int len;
unsigned long doneMillis;
unsigned long timeAfterMsg;
unsigned long ltrDuration;
unsigned long previousLtrMillis;
int currentLtrIdx;
bool msgDone;
};
const int scriptLength = 8;
ScriptedMessage msgScript[scriptLength] = {ScriptedMessage(willByersMsg, 9, 2500), ScriptedMessage(rightHereMsg, 9, 2500),
ScriptedMessage(helpMsg, 4, 2500),
ScriptedMessage(runMsg, 3, 2100), ScriptedMessage(runMsg, 3, 2100), ScriptedMessage(runMsg, 3, 2100),
ScriptedMessage(demogorgonMsg, 10, 2000), ScriptedMessage(randomMsg, 25, 2200, 50)
};
// Generate a random message
void generate_random_message() {
for (int iltr = 0; iltr < 25; ++iltr) {
int letterIdx = random(0, 26);
randomMsg[iltr] = alphabet[letterIdx];
}
}
// Handle presses to the magic 8 ball button
void magic_8_ball_button_pushed() {
send_letter(blank);
needMagic8BallAnswer = true;
}
// Answer magic 8 ball questions
void magic_8_ball_answer() {
if (needMagic8BallAnswer) {
send_letter(blank, 500);
int answer = random(0, 3);
// yes, we're using delay() here because I DO want to lock up the Arduino
switch (answer) {
case (0):
// yes
for (int iltr = 0; iltr < 3; ++iltr) send_letter(yesMsg[iltr], 500);
break;
case (1):
// maybe
for (int iltr = 0; iltr < 5; ++iltr) send_letter(maybeMsg[iltr], 500);
break;
case (2):
// no
for (int iltr = 0; iltr < 2; ++iltr) send_letter(noMsg[iltr], 500);
break;
}
send_letter(blank, 2000);
needMagic8BallAnswer = false;
}
}
// Set the current message in the script if we're done with the last one, and we're past
// the post-message delay
void set_current_message() {
if (msgScript[currentMsgIdx].is_done()) {
msgScript[currentMsgIdx].reset();
currentMsgIdx = (currentMsgIdx + 1) % scriptLength;
}
}
void setup() {
// set pins to output to control the shift register
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
// fill random message (can move this to loop to randomize during run)
generate_random_message();
// set pin for button and attach an interrupt
pinMode(buttonPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(2), magic_8_ball_button_pushed, LOW);
}
void loop() {
// Avoiding delay() keeps the Arduino responsive
currentMillis = millis(); // capture the latest value of millis()
magic_8_ball_answer();
msgScript[currentMsgIdx].send_next_letter();
set_current_message();
}