All done, I think.

This commit is contained in:
Neale Pickett 2020-12-13 21:07:20 -07:00
parent 5f30ec894f
commit 1b7a1ecbae
5 changed files with 408 additions and 78 deletions

View File

@ -9,9 +9,6 @@
// Frequencies in octave 7 // Frequencies in octave 7
const uint16_t freqs[] = { const uint16_t freqs[] = {
3520, // A
3729, // A#
3951, // B
2093, // C 2093, // C
2217, // C# 2217, // C#
2349, // D 2349, // D
@ -21,6 +18,9 @@ const uint16_t freqs[] = {
2960, // F# 2960, // F#
3136, // G 3136, // G
3322, // G# 3322, // G#
3520, // A
3729, // A#
3951, // B
}; };
const int scale[] = { const int scale[] = {
@ -99,11 +99,15 @@ OCTAVE_DONE:
tune = NULL; tune = NULL;
NoTone(); NoTone();
return false; return false;
case 'a' ... 'g': case 'a' ... 'b':
++octave;
case 'c' ... 'g':
++octave; ++octave;
note = scale[*tune - 'a'] + (12 * octave); note = scale[*tune - 'a'] + (12 * octave);
break; break;
case 'A' ... 'G': case 'A' ... 'B':
++octave;
case 'C' ... 'G':
note = scale[*tune - 'A'] + (12 * octave); note = scale[*tune - 'A'] + (12 * octave);
break; break;
case '_': case '_':

View File

@ -27,14 +27,14 @@ private:
uint16_t baseDuration; uint16_t baseDuration;
}; };
#define TUNE_STAYIN_ALIVE "E- F P a- P2 E-2 P2 C P <B- C E- P <B- C P E- P2 <B- P C P E- P F P a- P" #define TUNE_STAYIN_ALIVE "E- F _ A- P2 E-2 P2 C P <B- C E- P <B- C P E- P2 <B- P C P E- P F P A- P"
#define TUNE_JINGLEBELLS_CHORUS1 "eee2eee2egc.d/e4" #define TUNE_JINGLEBELLS_CHORUS1 "eee2eee2egc.d/e4"
#define TUNE_JINGLEBELLS_CHORUS2 "fff.f/feee/e/edded2g2" #define TUNE_JINGLEBELLS_CHORUS2 "fff.f/feee/e/edded2g2"
#define TUNE_JINGLEBELLS_CHORUS3 "fff.f/feee/e/ggfdc2._" #define TUNE_JINGLEBELLS_CHORUS3 "fff.f/feee/e/ggfdc2._"
#define TUNE_JINGLEBELLS_MELODY1 "GedcG2.G/G/GedcA4" #define TUNE_JINGLEBELLS_MELODY1 "GedcG2.G/G/GedcA4"
#define TUNE_JINGLEBELLS_MELODY2 "AfedB2._ ggfde2._" #define TUNE_JINGLEBELLS_MELODY2 "AfedB2._ggfde2._"
#define TUNE_JINGLEBELLS_MELODY3 "Afedgggg agfdc2g2" #define TUNE_JINGLEBELLS_MELODY3 "Afedggggagfdc2g2"
#define TUNE_JINGLEBELLS_CHORUS TUNE_JINGLEBELLS_CHORUS1 TUNE_JINGLEBELLS_CHORUS2 TUNE_JINGLEBELLS_CHORUS1 TUNE_JINGLEBELLS_CHORUS3 #define TUNE_JINGLEBELLS_CHORUS TUNE_JINGLEBELLS_CHORUS1 TUNE_JINGLEBELLS_CHORUS2 TUNE_JINGLEBELLS_CHORUS1 TUNE_JINGLEBELLS_CHORUS3
#define TUNE_JINGLEBELLS_MELODY TUNE_JINGLEBELLS_MELODY1 TUNE_JINGLEBELLS_MELODY2 TUNE_JINGLEBELLS_MELODY1 TUNE_JINGLEBELLS_MELODY3 #define TUNE_JINGLEBELLS_MELODY TUNE_JINGLEBELLS_MELODY1 TUNE_JINGLEBELLS_MELODY2 TUNE_JINGLEBELLS_MELODY1 TUNE_JINGLEBELLS_MELODY3
#define TUNE_JINGLEBELLS TUNE_JINGLEBELLS_MELODY TUNE_JINGLEBELLS_CHORUS #define TUNE_JINGLEBELLS TUNE_JINGLEBELLS_MELODY TUNE_JINGLEBELLS_CHORUS

View File

@ -11,9 +11,16 @@ bool Pulse::Tick() {
unsigned long now = millis(); unsigned long now = millis();
if (now >= nextEventMillis) { if (now >= nextEventMillis) {
nextEventMillis = now + period; Until(period, now);
return true; return true;
} }
return false; return false;
} }
void Pulse::Until(unsigned long next, unsigned long now) {
nextEventMillis = now + next;
}
void Pulse::Until(unsigned long next) {
Until(next, millis());
}

10
pulse.h
View File

@ -1,5 +1,11 @@
#pragma once #pragma once
#define MILLISECOND 1L
#define SECOND (1000 * MILLISECOND)
#define MINUTE (60 * SECOND)
#define HOUR (60 * MINUTE)
#define DAY (24 * HOUR)
class Pulse { class Pulse {
public: public:
Pulse(unsigned long period); Pulse(unsigned long period);
@ -7,6 +13,10 @@ public:
/** Tick tells you if a period has elapsed. */ /** Tick tells you if a period has elapsed. */
bool Tick(); bool Tick();
/** Until sets the duration of the next period. */
void Until(unsigned long next);
void Until(unsigned long next, unsigned long now);
unsigned long period; unsigned long period;
unsigned long nextEventMillis; unsigned long nextEventMillis;
}; };

View File

@ -1,55 +1,69 @@
#include <Adafruit_GFX.h> #include <Adafruit_GFX.h>
#include <Adafruit_MPR121.h>
#include <Adafruit_PCD8544.h> #include <Adafruit_PCD8544.h>
#include <FastLED.h> #include <FastLED.h>
#include <Fonts/FreeSerif9pt7b.h> #include <Keyboard.h>
#include <SPI.h> #include <SPI.h>
#include "morse.h" #include "morse.h"
#include "musicplayer.h" #include "musicplayer.h"
#include "paj7620.h" #include "paj7620.h"
#include "pulse.h" #include "pulse.h"
#include "riddler.h"
#define NUM_PUZZLES 6 #define NUM_PUZZLES 4
// WS2812 LEDs // WS2812 LEDs
#define LEDS_PIN 1 #define LEDS_PIN 13
#define NUM_LEDS NUM_PUZZLES #define NUM_LEDS NUM_PUZZLES
CRGB leds[NUM_LEDS]; CRGB leds[NUM_LEDS];
CHSV ColorSolved = CHSV(32, 200, 40); CHSV ColorSolved = CHSV(128, 200, 40);
CHSV ColorUnsolved = CHSV(32, 200, 40);
CHSV ColorBlack = CHSV(0, 0, 0);
// Laser // Laser
#define LASER_PIN 0 #define LASER_PIN 7
// Photoresistor // Photoresistor
#define PHOTO_PIN A0 #define PHOTO_PIN A0
// Piezo buzzer // Piezo buzzer
#define BUZZER_PIN 11 #define BUZZER_PIN 12
MusicPlayer mp = MusicPlayer(BUZZER_PIN); MusicPlayer mp = MusicPlayer(BUZZER_PIN);
// Display pin connections. LED needs to be PWM capable. // Display pin connections. LED needs to be PWM capable.
#define DISPLAY_WIDTH 84 #define DISPLAY_WIDTH 84
#define DISPLAY_HEIGHT 48 #define DISPLAY_HEIGHT 48
#define DISPLAY_SCE 4 #define DISPLAY_SCE 4
#define DISPLAY_RST 7 #define DISPLAY_RST 6
#define DISPLAY_DC 8 #define DISPLAY_DC 5
#define DISPLAY_LED 9 #define DISPLAY_LED 9
Adafruit_PCD8544 display = Adafruit_PCD8544(DISPLAY_DC, DISPLAY_SCE, DISPLAY_RST); Adafruit_PCD8544 display = Adafruit_PCD8544(DISPLAY_DC, DISPLAY_SCE, DISPLAY_RST);
// Morse code stuff // Morse code stuff
#define DIT_DURATION 100 #define DIT_DURATION (100 * MILLISECOND)
// Touch sensor
Adafruit_MPR121 cap;
void yay() {
mp.Play(120 * 4, TUNE_YAY);
}
void boo() {
mp.Play(120 * 4, TUNE_BOO);
}
void setup() { void setup() {
FastLED.addLeds<WS2812, LEDS_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(96);
// Turn on backlight // Turn on backlight
pinMode(DISPLAY_LED, OUTPUT); pinMode(DISPLAY_LED, OUTPUT);
analogWrite(9, 64); analogWrite(DISPLAY_LED, 64);
// Turn on display // Turn on display
display.begin(); display.begin();
display.setContrast(50); display.setContrast(50);
display.display();
FastLED.addLeds<WS2812, LEDS_PIN, GRB>(leds, NUM_LEDS);
//FastLED.setBrightness(96);
// Gesture sensor // Gesture sensor
while (paj7620Init()) { while (paj7620Init()) {
@ -58,62 +72,103 @@ void setup() {
display.display(); display.display();
} }
while (!cap.begin()) {
display.clearDisplay();
display.print("MPR121?");
display.display();
}
pinMode(LASER_PIN, OUTPUT); pinMode(LASER_PIN, OUTPUT);
pinMode(PHOTO_PIN, INPUT); pinMode(PHOTO_PIN, INPUT);
// Riddler symbol // Riddler symbol
display.clearDisplay(); poem("Touch White", "Wire", "", "Caution: Laser");
display.drawBitmap( display.setTextSize(2);
(DISPLAY_WIDTH - riddler_width) / 2, display.setCursor(70, 32);
(DISPLAY_HEIGHT - riddler_height) / 2, display.print('\x19');
riddler_bits,
riddler_width,
riddler_height,
0xffff);
display.display(); display.display();
randomSeed(analogRead(PHOTO_PIN));
// Be a USB keyboard
Keyboard.begin();
// Hello! // Hello!
mp.Play(120 * 4, TUNE_YAY); yay();
}
// returns true if it actually printed the poem
// in other words, if we just got foreground
bool poem(const char *s1, const char *s2, const char *s3, const char *s4) {
static char *last_s1 = NULL;
if (last_s1 != s1) {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print(s1);
display.setCursor(0, 8);
display.print(s2);
display.setCursor(0, 16);
display.print(s3);
display.setCursor(0, 24);
display.print(s4);
display.display();
last_s1 = s1;
return true;
}
return false;
}
CHSV solvedPoem(bool fg) {
if (fg) {
poem("Solved!", "", "", "");
}
return ColorSolved;
} }
CHSV loop_morse(bool fg) { CHSV loop_morse(bool fg) {
static Pulse pulse = Pulse(DIT_DURATION); static Pulse pulse = Pulse(DIT_DURATION);
static MorseEncoder enc = MorseEncoder("CQ CQ KD7OQI"); static MorseEncoder enc = MorseEncoder("CQ");
static uint16_t errors = 0; static uint16_t errors = 0;
bool solved = false; static bool solved = false;
int recv = analogRead(PHOTO_PIN); int recv = analogRead(PHOTO_PIN);
bool error = ((recv >= 512) != enc.Transmitting); bool error = ((recv >= 512) != enc.Transmitting);
CHSV color; CHSV color;
if (solved) { if (solved) {
return ColorSolved; return solvedPoem(fg);
} else if (error) { } else if (!fg) {
digitalWrite(LASER_PIN, false);
return ColorUnsolved;
}
poem(
"Help my sensor",
"See the light;",
"Got to get the",
"Timing right!");
color = CHSV(0, 255, recv >> 2);
if (error) {
++errors; ++errors;
color = CHSV(0, 255, recv >> 2);
} else {
color = CHSV(128, 255, recv >> 2);
} }
if (!pulse.Tick()) { if (!pulse.Tick()) {
return color; return color;
} }
if (fg) {
display.clearDisplay();
display.setFont();
display.setCursor(0, 0);
display.print("The Morse One");
display.display();
}
if (enc.Tick()) { if (enc.Tick()) {
digitalWrite(LASER_PIN, enc.Transmitting); digitalWrite(LASER_PIN, enc.Transmitting);
} else { } else {
// We've sent the whole thing // We've sent the whole thing
if (errors < 500) { if (errors < 500) {
solved = true; solved = true;
yay();
return solvedPoem(fg);
} }
enc.SetText("HO HO HO ARK"); boo();
enc.SetText("HO HO HO");
enc.Quiet(30); enc.Quiet(30);
errors = 0; errors = 0;
} }
@ -125,8 +180,8 @@ CHSV loop_morse(bool fg) {
#define DOWN 25 #define DOWN 25
#define RIGHT 26 #define RIGHT 26
#define LEFT 27 #define LEFT 27
#define IN 15 #define IN 'B'
#define OUT 9 #define OUT 'A'
const char KonamiCode[] = { const char KonamiCode[] = {
UP, UP,
UP, UP,
@ -143,20 +198,24 @@ const char KonamiCode[] = {
CHSV loop_konami(bool fg) { CHSV loop_konami(bool fg) {
static bool solved = false; static bool solved = false;
static int pos = 0; static int pos = 0;
static Pulse pulse = Pulse(100); static Pulse pulse = Pulse(100 * MILLISECOND);
CHSV color; CHSV color;
uint8_t gesture; uint8_t gesture;
if (solved) { if (solved) {
return ColorSolved; return solvedPoem(fg);
} else if (!fg) {
return ColorUnsolved;
} }
poem(
"wave it in:",
"it codifies",
"beefy men with",
"30 lives");
uint8_t prox = 0; uint8_t prox = 0;
if (!paj7620ReadReg(0x6c, 1, &prox)) { paj7620ReadReg(0x6c, 1, &prox);
display.fillRect(0, 0, 84, 4, 0);
display.fillRect(0, 0, min(prox / 3, 84), 4, 1);
}
color = CHSV(0, 255, prox); color = CHSV(0, 255, prox);
if (!pulse.Tick()) { if (!pulse.Tick()) {
@ -193,13 +252,14 @@ CHSV loop_konami(bool fg) {
} }
if (out) { if (out) {
if (out == KonamiCode[pos]) { if (out == KonamiCode[pos]) {
display.fillRect(pos*6, 40, 6, 8, 0); display.fillRect(pos * 6, 40, 6, 8, 0);
display.setCursor(pos * 6, 40); display.setCursor(pos * 6, 40);
++pos; ++pos;
} else { } else {
display.fillRect(0, 40, 84, 8, 0); display.fillRect(0, 40, 84, 8, 0);
display.setCursor(0, 40); display.setCursor(0, 40);
pos = 0; pos = 0;
boo();
} }
display.print(out); display.print(out);
} }
@ -207,27 +267,259 @@ CHSV loop_konami(bool fg) {
if (KonamiCode[pos] == 0) { if (KonamiCode[pos] == 0) {
solved = true; solved = true;
mp.Play(120 * 4, TUNE_YAY); yay();
color = ColorSolved; return solvedPoem(fg);
} }
display.display(); display.display();
return color; return color;
} }
void beHappy() { #define ROUNDS_TO_WIN 5
if (!mp.KeepPlaying()) { const char *inputNames[] = {
mp.Play(76 * 4, TUNE_JINGLEBELLS); "yellow",
"green",
"red",
"blue",
};
const uint8_t numInputNames = sizeof(inputNames) / sizeof(*inputNames);
display.clearDisplay(); CHSV simonSound(int num) {
display.setFont(&FreeSerif9pt7b); if (-1 == num) {
display.setCursor(0, 12); mp.NoTone();
display.print("Happy"); return ColorBlack;
display.setCursor(0, 29); } else {
display.print("Holiday,"); uint16_t hue;
display.setCursor(0, 46); switch (num) {
display.print("Martin!"); case 0:
display.display(); mp.Tone(60);
hue = HUE_YELLOW;
break;
case 1:
mp.Tone(55);
hue = HUE_GREEN;
break;
case 2:
mp.Tone(50);
hue = HUE_RED;
break;
case 3:
mp.Tone(45);
hue = HUE_BLUE;
break;
}
return CHSV(hue, 255, 64);
}
}
#define SIMON_TONE_DURATION (300 * MILLISECOND)
#define SIMON_QUIET_DURATION (SIMON_TONE_DURATION * 2)
#define SIMON_INPUT_TIMEOUT (4 * SECOND)
CHSV loop_simon(bool fg, uint16_t touched, uint16_t justTouched) {
static uint8_t round = 0;
static uint8_t sequencePosition = 0;
static bool solved = false;
static uint8_t sequence[ROUNDS_TO_WIN] = {0};
static uint8_t state = 0;
static Pulse pulse = Pulse(0);
static CHSV color = ColorBlack;
bool ticked = pulse.Tick();
if (solved) {
return solvedPoem(fg);
} else if (!fg) {
return ColorUnsolved;
}
bool reset = poem(
"For a fun game",
"we can play:",
"You repeat all",
"that I say!");
if (reset) {
state = 0;
}
uint8_t input = sequence[sequencePosition];
switch (state) {
case 0: { // quiet
// Beginning of a round: be quiet for a bit
color = simonSound(-1);
pulse.Until(SIMON_QUIET_DURATION);
// Pick an input at random for the next sequence item
sequence[round] = random(numInputNames);
// Start the sequencePosition loop at position 0
sequencePosition = 0;
state = 1;
break;
}
case 1: { // Wait for a tick
if (!ticked) {
break;
}
state = 2;
break;
}
case 2: { // beep
// set the buzzer and color
color = simonSound(input);
pulse.Until(SIMON_TONE_DURATION);
state = 3;
break;
}
case 3: { // Wait for a tick
if (!ticked) {
break;
}
state = 4;
break;
}
case 4: { // no beep
// Stop the tone and color
color = simonSound(-1);
// Is that the last one this round?
if (sequencePosition == round) {
// Start listening for input
pulse.Until(SIMON_INPUT_TIMEOUT);
sequencePosition = 0;
state = 5;
} else {
// We're ready to play the next tone
++sequencePosition;
pulse.Until(SIMON_TONE_DURATION);
state = 1;
}
break;
}
case 5: { // Listen for input
if (ticked) {
// Time's up!
state = 7;
}
if (justTouched) {
if (justTouched == bit(input)) {
// That's right!
color = simonSound(input);
state = 6;
} else {
// Wrong!
state = 7;
}
}
break;
}
case 6: { // We're playing the right tone, keep doing that until they let go
if (!touched) {
color = simonSound(-1);
if (sequencePosition == round) {
// They got everything this round, increase difficulty
state = 8;
} else {
// Listen for the next one
++sequencePosition;
pulse.Until(SIMON_INPUT_TIMEOUT);
state = 5;
}
}
break;
}
case 7: { // Wrong!
boo();
pulse.Until(1 * SECOND);
round = 0;
sequencePosition = 0;
state = 9;
break;
}
case 8: { // Increase difficulty!
++round;
if (round == ROUNDS_TO_WIN) {
yay();
solved = true;
return solvedPoem(true);
}
sequencePosition = 0;
state = 9;
break;
}
case 9: { // wait for tick
if (!ticked) {
break;
}
sequencePosition = 0;
state = 0;
break;
}
default:
// This should never happen...
state = 0;
break;
}
return color;
}
#define KEYBOARD_INPUT_ROUNDS 6
CHSV loop_keyboard(bool fg, uint16_t justTouched) {
static bool solved = false;
static uint8_t remaining = KEYBOARD_INPUT_ROUNDS;
static uint8_t inputName = 0;
static Pulse pulse = Pulse(6 * SECOND);
if (solved) {
return solvedPoem(fg);
} else if (!fg) {
return ColorUnsolved;
}
poem(
"Let me talk to",
"a computer,",
"You will see I",
"get much cuter");
if (pulse.Tick()) {
inputName = random(numInputNames);
Keyboard.print("\nNow touch ");
Keyboard.print(inputNames[inputName]);
Keyboard.print("...");
}
if (justTouched) {
Keyboard.print(" ");
if (justTouched == bit(inputName)) {
Keyboard.print("correct.");
--remaining;
} else {
Keyboard.print("wrong.");
boo();
remaining = KEYBOARD_INPUT_ROUNDS;
}
if (remaining == 0) {
Keyboard.print("\nGOOD JOB, HUMAN\n");
yay();
solved = true;
return solvedPoem(true);
}
pulse.Until(0); // Pick another wire immediately
}
return ColorBlack;
}
void beHappy() {
while (true) {
if (!mp.KeepPlaying()) {
mp.Play(76 * 4, TUNE_JINGLEBELLS);
poem(
"The final act",
"enjoy you must",
"I type for you",
"Some C++!");
Keyboard.print("Happy Holidy, Martin! https://github.com/nealey/puzzle-box\n");
}
} }
} }
@ -240,6 +532,16 @@ void loop() {
mp.KeepPlaying(); mp.KeepPlaying();
// Read capacative touch sensors
static uint16_t lastTouched = 0;
uint16_t touched = cap.touched();
uint16_t justTouched = (lastTouched ^ touched) & touched;
if (bitRead(justTouched, 4)) {
current = (current + 1) % NUM_PUZZLES;
tone(BUZZER_PIN, 220 * (current + 1), 220);
}
for (int i = 0; i < NUM_PUZZLES; ++i) { for (int i = 0; i < NUM_PUZZLES; ++i) {
CHSV color = CHSV(0, 0, 0); CHSV color = CHSV(0, 0, 0);
bool fg = (current == i); bool fg = (current == i);
@ -251,6 +553,12 @@ void loop() {
case 1: case 1:
color = loop_konami(fg); color = loop_konami(fg);
break; break;
case 2:
color = loop_simon(fg, touched, justTouched);
break;
case 3:
color = loop_keyboard(fg, justTouched);
break;
} }
if (color != lastColors[i]) { if (color != lastColors[i]) {
lastColors[i] = color; lastColors[i] = color;
@ -262,11 +570,12 @@ void loop() {
leds[i] = color; leds[i] = color;
} }
if (allSolved) {
beHappy();
}
if (writeLEDs) { if (writeLEDs) {
FastLED.show(); FastLED.show();
} }
lastTouched = touched;
if (allSolved) {
beHappy();
}
} }