#include <Button_SL.hpp>
#include <Streaming.h>
Print &cout {Serial};
using namespace Btn;
///
/// Global Constants
///
constexpr uint8_t RGB_LED_PINS[] {A1, A2, A3};
constexpr uint8_t MATRIX_COLS {3};
constexpr uint8_t MATRIX_ROWS {MATRIX_COLS};
constexpr uint32_t BLINK_INTERVAL_MS {400};
//////////////////////////////////////////////////////////////////////////////
/// Helperclass for timer operations
///
//////////////////////////////////////////////////////////////////////////////
class Timer {
public:
void start() { timeStamp = millis(); }
bool operator()(const uint32_t duration) const { return (millis() - timeStamp >= duration) ? true : false; }
private:
uint32_t timeStamp {0};
};
///
/// Own Datatypes
///
enum class Status : uint8_t { none, col, row, save, reset, result, waitForReset};
enum ColorNum {off, red, green, blue, cyan, yellow, magenta, white};
struct PlayingField {
const uint8_t pin;
bool saved;
uint8_t value;
};
///
/// Used Functions
///
//
// Button query
//
template <size_t N> Status checkButtons(ButtonSL (&btns)[N]) {
for (uint8_t i = 0; i < N; ++i) {
if (btns[i].tick() != ButtonState::notPressed) { return static_cast<Status>(i + 1); }
}
return Status::none;
}
//
// toggle LEDs
//
template <size_t N> void toggleLed(PlayingField (&data)[N], uint16_t idx) {
if (!data[idx].saved && idx < N) { digitalWrite(data[idx].pin, !digitalRead(data[idx].pin)); }
}
//
// switch LED off
//
template <size_t N> void switchLedOff(PlayingField (&data)[N], uint16_t idx) {
if (!data[idx].saved && idx < N) { digitalWrite(data[idx].pin, LOW); }
}
//
// switch RGB-LED (off / color)
//
template <size_t N> void setRGBLed(const uint8_t (&pin)[N], uint8_t colorNum) {
uint32_t colorBits {0b00000000111101110011001010100000};
uint8_t colors = (colorBits >> (colorNum * 3)) & 0b111;
for (size_t i {0}; i < N; ++i) { digitalWrite(pin[i], colors << i & 0b100); }
}
//
// Movement on the game matrix. Calculate the right array index
//
template <size_t N> bool doMove(PlayingField (&data)[N], uint16_t &idx, uint8_t increment) {
bool allFieldsSet {false};
auto start = idx;
do {
idx += increment;
if (increment > 1) {
idx = (idx > (N - 1)) ? (idx - (N - 1)) % increment : idx;
} else {
idx = (idx > (N - 1)) ? 0 : idx;
}
if (idx == start) { allFieldsSet = true; } // Abort condition
if (!data[idx].saved) { break; } // Exit loop if a free field is found
} while (!allFieldsSet);
return allFieldsSet;
}
//
// Check the result when the input has been completed
//
template <size_t N> bool checkResult(PlayingField (&result)[N]) {
int16_t rowSum[MATRIX_ROWS] {0, 0, 0};
int16_t colSum[MATRIX_COLS] {0, 0, 0};
int16_t diagSum1 {0};
int16_t diagSum2 {0};
for (size_t row {0}; row < MATRIX_ROWS; ++row) {
for (size_t col {0}; col < MATRIX_COLS; ++col) {
uint8_t idx = row * MATRIX_ROWS + col;
cout << result[idx].value << " ";
rowSum[row] += result[idx].value;
colSum[col] += result[idx].value;
if (row == col) {
diagSum1 += result[idx].value; // Main diagonal
}
if (row + col == MATRIX_COLS - 1) {
diagSum2 += result[idx].value; // Secondary diagonal
}
}
}
cout << endl;
// Ausgabe der Summen
// cout << "Reihensummen: ";
// for (size_t i {0}; i < MATRIX_ROWS; ++i) { cout << rowSum[i] << " "; }
// cout << endl;
// cout << "Spaltensummen: ";
// for (size_t i {0}; i < MATRIX_COLS; ++i) { cout << colSum[i] << " "; }
// cout << endl;
// cout << "Hauptdiagonale: " << diagSum1 << endl;
// cout << "Nebendiagonale: " << diagSum2 << endl;
bool isRowEqual {true};
bool isColEqual {true};
int16_t compare = rowSum[0];
for (size_t i = 1; i < MATRIX_ROWS; ++i) {
if (rowSum[i] != compare) {
isRowEqual = false;
break;
}
}
for (size_t i = 0; i < MATRIX_COLS; ++i) {
if (colSum[i] != compare) {
isRowEqual = false;
break;
}
}
return isRowEqual && isColEqual && (diagSum1 == compare) && (diagSum2 == compare);
}
//
// Clear Data
//
template <size_t N> void resetData(PlayingField (&data)[N]) {
for (auto &d : data) {
digitalWrite(d.pin, LOW);
d.saved = false;
d.value = 0;
}
}
///
/// Global variables / objects
///
PlayingField pfData[] {
{2, false, 0},
{3, false, 0},
{4, false, 0},
{5, false, 0},
{6, false, 0},
{7, false, 0},
{8, false, 0},
{9, false, 0},
{10, false, 0}
};
ButtonSL bArray[] {{11}, {12}, {13}, {A0}};
Timer waitBlink;
// | 8 | 1 | 6 |
// | 3 | 5 | 7 |
// | 4 | 9 | 2 |
///
/// Main Program
///
void setup() {
Serial.begin(115200);
for (auto &p : pfData) { pinMode(p.pin, OUTPUT); }
for (auto &p : RGB_LED_PINS) { pinMode(p, OUTPUT); }
for (auto &b : bArray) {
b.begin();
b.setDebounceTime_ms(40);
}
setRGBLed(RGB_LED_PINS, blue);
}
void fsm() {
static Status status {Status::none};
static uint16_t index {}; // index playfield array (leds)
static uint16_t prevIndex {}; // previos index playfield array ( for moving - switch previous led off )
static uint8_t saveCounter {}; // Storage counter -> index for data array (result)
static bool isSaving {false}; // needed to prevent saving errors
switch (status) {
case Status::none:
status = checkButtons(bArray);
break;
case Status::col:
case Status::row:
switchLedOff(pfData, prevIndex);
doMove(pfData, index, (status == Status::col) ? 1 : MATRIX_COLS);
waitBlink.start();
status = Status::none;
break;
case Status::save:
pfData[index].saved = true;
pfData[index].value = ++saveCounter;
cout << "Saved Nr. " << saveCounter << " at Index: " << index << endl;
digitalWrite(pfData[index].pin, HIGH);
if (doMove(pfData, index, 1) == true) { // true if all fields are set
status = Status::result;
} else {
waitBlink.start();
status = Status::none;
}
break;
case Status::reset:
resetData(pfData);
setRGBLed(RGB_LED_PINS, blue);
index = saveCounter = 0;
isSaving = false;
cout << endl << endl;
status = Status::none;
break;
case Status::result:
checkResult(pfData) ? setRGBLed(RGB_LED_PINS, green) : setRGBLed(RGB_LED_PINS, red);
status =Status::waitForReset;
[[falltrough]]
case Status::waitForReset:
if (checkButtons(bArray) == Status::reset) { status = Status:: reset; }
break;
default:
break;
}
if (waitBlink(BLINK_INTERVAL_MS)) {
prevIndex = index;
toggleLed(pfData, index);
waitBlink.start();
}
}
void loop() { fsm(); }
col
row
save
reset
blue:game active
green: result is correct
red: result is incorrect