#include <Arduino.h>
#include "function_objects.h"
// Game config
constexpr auto start_level = 3;
constexpr auto max_level = 50;
constexpr auto allow_repeat = false;
constexpr auto max_error = 3; // 0 means unlimited
constexpr auto max_hint = 2; // 0 means unlimited
constexpr auto btn_up_debounce_ms = 50;
constexpr auto button_timeout_s = 60; // 0 means unlimited
constexpr auto hint_timeout_s = 10; // 0 means no hint, otherwise repeated with this interval
constexpr auto max_hint_blinks = 2; // 0 means forever
// HW config
constexpr auto led_reset_pin = A3;
constexpr auto led_latch_pin = A2;
constexpr auto led_clock_pin = A1;
constexpr auto led_data_pin = A0;
constexpr byte btn_pins[] = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
constexpr auto btn_count = sizeof(btn_pins);
constexpr byte random_source_pins[] = {A4, A5};
constexpr auto random_source_count = sizeof(random_source_pins);
// Utils
uint64_t millis64() {
static uint32_t low32, high32;
uint32_t new_low32 = millis();
if (new_low32 < low32) high32++;
low32 = new_low32;
return (uint64_t) high32 << 32 | low32;
}
constexpr uint16_t mask_led(byte led)
{
return 1 << led;
}
constexpr uint16_t row_masks[] = {
mask_led(0),
mask_led(1) | mask_led(2),
mask_led(3) | mask_led(4) | mask_led(5),
mask_led(6) | mask_led(7) | mask_led(8) | mask_led(9)
};
constexpr uint16_t all_leds_mask = row_masks[0] | row_masks[1] | row_masks[2] | row_masks[3];
constexpr uint16_t left_row_masks[] = {
mask_led(6) | mask_led(3) | mask_led(1) | mask_led(0),
mask_led(7) | mask_led(4) | mask_led(2),
mask_led(8) | mask_led(5),
mask_led(9)
};
constexpr uint16_t right_row_masks[] = {
mask_led(9) | mask_led(5) | mask_led(2) | mask_led(0),
mask_led(8) | mask_led(4) | mask_led(1),
mask_led(7) | mask_led(3),
mask_led(6)
};
// Display logic
uint16_t current_display;
void display(uint16_t data)
{
current_display = data;
digitalWrite(led_latch_pin, LOW);
shiftOut(led_data_pin, led_clock_pin, MSBFIRST, data >> 8);
shiftOut(led_data_pin, led_clock_pin, MSBFIRST, data);
digitalWrite(led_latch_pin, HIGH);
}
void light_single(byte led)
{
display(mask_led(led));
}
void flash_all(unsigned long ms)
{
display(all_leds_mask);
delay(ms);
display(0);
}
// Game logic
int gather_random()
{
int result = 0;
for (byte i = 0; i < random_source_count; ++i)
{
result += analogRead(random_source_pins[i]);
}
return result;
}
byte level;
byte sequence_len;
byte sequence[max_level];
byte error_count;
byte hint_count;
void finale()
{
display(0);
for (int i = 0; i < 5; ++i)
{
light_single(0);
delay(20);
light_single(2);
delay(20);
light_single(5);
delay(20);
light_single(9);
delay(20);
light_single(8);
delay(20);
light_single(7);
delay(20);
light_single(6);
delay(20);
light_single(3);
delay(20);
light_single(1);
delay(20);
}
for (int i = 0; i < 5; ++i)
{
light_single(0);
delay(20);
light_single(1);
delay(20);
light_single(3);
delay(20);
light_single(6);
delay(20);
light_single(7);
delay(20);
light_single(8);
delay(20);
light_single(9);
delay(20);
light_single(5);
delay(20);
light_single(2);
delay(20);
}
display(all_leds_mask ^ mask_led(4));
delay(200);
display(left_row_masks[0] | left_row_masks[1] | left_row_masks[2] | left_row_masks[3]);
delay(100);
display(left_row_masks[0] | left_row_masks[1] | left_row_masks[2]);
delay(50);
display(left_row_masks[0] | left_row_masks[1]);
delay(50);
display(left_row_masks[0]);
delay(50);
display(0);
delay(200);
display(right_row_masks[0] | right_row_masks[1] | right_row_masks[2] | right_row_masks[3]);
delay(100);
display(right_row_masks[0] | right_row_masks[1] | right_row_masks[2]);
delay(50);
display(right_row_masks[0] | right_row_masks[1]);
delay(50);
display(right_row_masks[0]);
delay(50);
display(0);
delay(200);
display(row_masks[0] | row_masks[1] | row_masks[2] | row_masks[3]);
delay(100);
display(row_masks[0] | row_masks[1] | row_masks[2]);
delay(50);
display(row_masks[0] | row_masks[1]);
delay(50);
display(row_masks[0]);
delay(50);
display(0);
delay(200);
display(0);
delay(2000);
}
void small_fail_anim()
{
display(0);
delay(500);
auto row_3_remaining_lives = row_masks[3];
for (int i = 0; i < error_count; ++i)
{
row_3_remaining_lives ^= mask_led(6 + i);
}
display(row_masks[3]);
delay(100);
display(row_masks[3] | row_masks[2]);
delay(100);
display(row_masks[3] | row_masks[2] | row_masks[1]);
delay(100);
display(row_masks[3] | row_masks[2] | row_masks[1] | row_masks[0]);
delay(100);
display(row_masks[3] | row_masks[2] | row_masks[1]);
delay(100);
display(row_masks[3] | row_masks[2]);
delay(100);
for (int i = 0; i < 5; ++i)
{
display(row_masks[3]);
delay(50);
display(row_3_remaining_lives);
delay(200);
}
delay(1000);
display(0);
}
void large_fail_anim()
{
small_fail_anim();
for (int i = 0; i < 4; ++i)
{
display(0);
delay(500);
display(all_leds_mask ^ mask_led(4));
delay(500);
}
display(0);
}
void reset()
{
Serial.println(F("Resetting sequence"));
sequence_len = 1; // First sequence will be 2;
sequence[0] = random(btn_count);
error_count = 0;
hint_count = 0;
}
int8_t wait_for_button_down(FunctionObject<bool()> button_wait_tick = FunctionObject<bool()>())
{
int8_t btn = -1;
Serial.println(F("Waiting for button down"));
while (btn == -1)
{
if (button_wait_tick && !button_wait_tick())
{
Serial.println("Button wait cancelled");
return -1;
}
for (byte i = 0; i < btn_count; ++i)
{
if (!digitalRead(btn_pins[i]))
{
if (btn != -1)
{ // Ignore ambigous button press
Serial.print(F("Buttons "));
Serial.print(btn);
Serial.print(F(" and "));
Serial.print(i);
Serial.println(F(" are both pressed"));
btn = -1;
break;
}
btn = i;
}
}
}
Serial.print(F("Button "));
Serial.print(btn);
Serial.println(F(" is down"));
return btn;
}
void wait_for_button_up(byte btn, FunctionObject<bool()> button_wait_tick = FunctionObject<bool()>())
{
Serial.print(F("Waiting for button "));
Serial.print(btn);
Serial.println(F(" up"));
restart_wait_for_button_up:
while (!digitalRead(btn_pins[btn])) {
if (button_wait_tick && !button_wait_tick())
{
Serial.println("Button wait cancelled");
return;
}
}
Serial.println(F("Button is up, start debounce check"));
auto wait_til = millis64() + btn_up_debounce_ms;
while (millis64() < wait_til)
{
if (button_wait_tick && !button_wait_tick())
{
Serial.println("Button wait cancelled");
return;
}
if (!digitalRead(btn_pins[btn]))
{
Serial.println(F("Button is bounced, return to waiting"));
goto restart_wait_for_button_up;
}
}
Serial.print(F("Button "));
Serial.print(btn);
Serial.println(F(" is up"));
}
void level_change(byte next_level)
{
Serial.println(F("Waiting for any button to be pressed to continue"));
display(0);
auto last_time = millis64();
auto btn = wait_for_button_down([&last_time]{
auto now = millis64();
if (now > last_time + 5000)
{
light_single(4);
delay(1);
display(0);
last_time = now;
}
return true;
});
last_time = millis64();
wait_for_button_up(btn, [btn, &last_time]{
auto now = millis64();
if (now > last_time + 20)
{
display(current_display ^ mask_led(btn));
last_time = now;
}
return true;
});
level = next_level;
Serial.print(F("Entering "));
Serial.print(level);
Serial.println(F(" level"));
}
void level_change()
{
level_change(level + 1);
}
void create_next_sequence()
{
if (sequence_len < level)
{
Serial.print(F("Adding one more, new length: "));
do
{
sequence[sequence_len] = random(btn_count);
}
while (allow_repeat || sequence[sequence_len - 1] == sequence[sequence_len]);
++sequence_len;
Serial.println(sequence_len);
}
else
{
Serial.print(F("Level "));
Serial.print(level);
Serial.println(F(" finished successfully."));
finale();
if (level <= max_level)
{
level_change();
}
reset();
create_next_sequence();
}
}
void display_sequence()
{
// Blinky start
for (int i = 0; i < 4; ++i)
{
display(0);
delay(100);
light_single(4);
delay(100);
}
display(0);
delay(1000);
Serial.print(F("Sequence: "));
for (int i = 0; i < sequence_len; ++i)
{
Serial.print(sequence[i]);
Serial.print(F(" "));
light_single(sequence[i]);
delay(500);
display(0);
delay(500);
}
Serial.println();
display(all_leds_mask ^ mask_led(4));
delay(5);
display(0);
}
bool read_back_sequence()
{
Serial.println(F("Reading sequence..."));
for (int i = 0; i < sequence_len; ++i)
{
Serial.print(F("Waiting for next button: "));
Serial.println(sequence[i]);
const auto wait_start = millis64();
auto last_hint_blink = wait_start;
byte hint_blinks = 0;
auto btn = wait_for_button_down([i, &wait_start, &last_hint_blink, &hint_blinks]{
auto now = millis64();
if (button_timeout_s > 0 && now >= wait_start + 1000ul * button_timeout_s)
{
Serial.println(F("Button timed out"));
return false;
}
if (hint_timeout_s > 0 && now >= last_hint_blink + 1000ul * hint_timeout_s
&& (max_hint_blinks == 0 || hint_blinks < max_hint_blinks)
&& (max_hint == 0 || hint_count < max_hint))
{
if (hint_blinks == 0)
{
++hint_count;
}
++hint_blinks;
Serial.print(F("Hinting. Hint: "));
Serial.print(hint_count);
Serial.print(" Blink: ");
Serial.println(hint_blinks);
last_hint_blink = now;
auto saved_display = current_display;
display(saved_display | mask_led(sequence[i]));
delay(1);
display(saved_display);
}
return true;
});
if (btn == -1)
{
Serial.println(F("Timed out waiting for button press"));
return false;
}
Serial.print(F("Got button: "));
Serial.println(btn);
if (btn == sequence[i])
{
light_single(btn);
wait_for_button_up(btn);
}
else
{
display(all_leds_mask ^ mask_led(btn)); // All but the pressed
wait_for_button_up(btn);
return false;
}
}
return true;
}
// "Main"
void setup() {
Serial.begin(9600);
pinMode(led_reset_pin, OUTPUT);
pinMode(led_latch_pin, OUTPUT);
pinMode(led_clock_pin, OUTPUT);
pinMode(led_data_pin, OUTPUT);
for (byte i = 0; i < btn_count; ++i)
{
pinMode(btn_pins[i], INPUT_PULLUP);
}
for (byte i = 0; i < random_source_count; ++i)
{
pinMode(random_source_pins[i], INPUT);
}
auto seed = gather_random();
delay(1);
seed += gather_random();
digitalWrite(led_reset_pin, HIGH);
digitalWrite(led_latch_pin, LOW);
digitalWrite(led_clock_pin, LOW);
digitalWrite(led_data_pin, LOW);
delay(1);
Serial.println(F("Led check"));
for (int i = 0; i < 10; ++i)
{
light_single(i);
seed += gather_random();
delay(100);
}
display(0);
seed += gather_random();
delay(200);
flash_all(100);
seed += gather_random();
/** /
seed = 0; // Fixed seed for testing
// */
randomSeed(seed);
Serial.print(F("Random seed: "));
Serial.println(seed);
Serial.println(F("Running"));
reset();
Serial.print(F("Starting at level "));
Serial.println(start_level);
level_change(start_level);
}
void loop() {
create_next_sequence();
display_sequence();
while (!read_back_sequence())
{
if (max_error > 0 && ++error_count > max_error)
{
Serial.println(F("Readback failed and max error reached. Resetting..."));
large_fail_anim();
level_change(level); // Just to require the button press, not actually changing level
reset();
return;
}
else
{
Serial.print(F("Readback failed. Error count: "));
Serial.println(error_count);
small_fail_anim();
delay(500);
}
display_sequence();
}
}