// TARGET
// ??
// REVISION HISTORY
#define VERSION "V0.1"
// 0.1 - First Release
// DEBUG Const
#define DEBUG true //set to true for debug output, false for no debug output
#define DEBUG_SERIAL \
if (DEBUG) Serial
// Add HW target (eventually boards installed)
// Add External Library
// Adafruit Neopixel
#include <Adafruit_NeoPixel.h>
// Display MAX7219
#include <stdio.h>
#include <MD_Parola.h> // include MajicDesigns Parola library
#include <MD_MAX72xx.h> // include MajicDesigns MAX72xx LED matrix library
#include <SPI.h> // include Arduino SPI library
// Add Internal Library
#include "BTN.h"
// Pin assignment
#define PIN_WS2812B 13
#define PIN_BUTTON {4, 22, 21, 25, 32, 33, 17, 16}
#define CS_PIN 5 // define CS (Chip Select) pin
// HW Constants
#define NUM_POINTS 8
#define NUM_LEDS_IN_POINT 3
#define NUM_PIXELS NUM_POINTS*NUM_LEDS_IN_POINT
#define BRIGHTNESS 255 // 255 max
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW //MD_MAX72XX::FC16_HW // this line defines our dot matrix hardware type (FC-16)
#define MAX_DEVICES 4 // define number of total cascaded modules
#define BRIGHTNESS_DISPLAY 3
// SW Constants
#define NUM_MAX_PLAYS 4
const uint8_t logo[] = {8, 0, 28, 106, 137, 129, 98, 28, 0};
#define NUM_MAX_CHAR_TEXT 40
// HW Global variables
Adafruit_NeoPixel ws2812b(NUM_PIXELS, PIN_WS2812B, NEO_GRB + NEO_KHZ800);
MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
MyBtn btn[NUM_POINTS];
int pinBtn[NUM_POINTS] = PIN_BUTTON;
// SW Global variables
enum gameStateEnum {INIT_LEDBATTLE, START_LEDBATTLE, GAME_LEDBATTLE, WINNER_LEDBATTLE, FINISH_LEDBATTLE};
enum gameStateEnum gameState = INIT_LEDBATTLE;
enum gameStateEnum gameStateOld = START_LEDBATTLE;
bool first = false;
uint32_t rgbPlay[NUM_MAX_PLAYS] = {0}; // Color of players
//bool readyPlay[NUM_MAX_PLAYS] = {0}; // Player ready to play
bool winPlay[NUM_MAX_PLAYS] = {0}; // true when the relative player won
uint16_t scorePlay[NUM_MAX_PLAYS] = {0}; // score of player
int8_t posPlay[NUM_MAX_PLAYS] = {-1}; // score of player
uint8_t numPlays = 0; // num of players
char textBuffer[NUM_MAX_CHAR_TEXT];
void setup() {
Serial.begin(9600);
DEBUG_SERIAL.println("SETUP");
ws2812b.begin();
for (int i=0; i<NUM_POINTS; i++) {
pinMode(pinBtn[i], INPUT_PULLUP);
btn[i].configPin(pinBtn[i]);
}
// initialize the dot matrix display
display.begin();
// set the intensity (brightness) of the display (choose a number between 0 and 15)
display.setIntensity(BRIGHTNESS_DISPLAY);
display.setZone(0,0,3);
display.setZone(1,0,1);
display.setZone(2,2,3);
display.addChar('$', logo);
// display version
display.displayReset();
display.displayClear();
display.setTextBuffer(VERSION);
display.setTextAlignment(PA_LEFT);
display.setSpeed(50);
display.setTextEffect(PA_SCROLL_LEFT, PA_SCROLL_LEFT);
randomSeed(analogRead(0));
delay(1000);
}
void loop() {
// First evaluation for first time in new state
if (gameStateOld != gameState) {
first = true;
gameStateOld = gameState;
}
// TODO: a cosa serve?
if (display.displayAnimate()) { // animate the display
display.displayReset(); // reset the current animation
}
switch (gameState) {
case INIT_LEDBATTLE:
if (first) {
first = false;
clearLeds();
clearBtnsAllEvents();
// Reset all data
memset(rgbPlay, 0, NUM_MAX_PLAYS * sizeof(uint32_t));
//memset(readyPlay, 0, NUM_MAX_PLAYS * sizeof(bool));
memset(winPlay, 0, NUM_MAX_PLAYS * sizeof(bool));
memset(scorePlay, 0, NUM_MAX_PLAYS * sizeof(uint16_t));
memset(posPlay, -1, NUM_MAX_PLAYS * sizeof(int8_t));
numPlays = 0;
// Splash text
display.displayClear();
display.displayReset();
display.setTextBuffer("Led Battle $");
display.setTextAlignment(PA_LEFT);
display.setSpeed(50);
display.setTextEffect(PA_SCROLL_LEFT, PA_SCROLL_LEFT);
// Color points
for (int i=0; i<NUM_POINTS; i++) {
setPointColor(i, ws2812b.gamma32(ws2812b.ColorHSV(random(1, 65535), 255, BRIGHTNESS)));
}
ws2812b.show();
DEBUG_SERIAL.println("INIT_LEDBATTLE");
}
animInitLedBattle();
for (int i=0; i<NUM_POINTS; i++) {
if (btn[i].wasReleased()) {
//DEBUG_SERIAL.print("PRESSED: ");
//DEBUG_SERIAL.println(i);
btn[i].clearReleased();
if (findPlayIndexInReadyPlayPos(i) >= 0) {
// Pressed again an already selected point means start of the game with current players
gameState = START_LEDBATTLE;
}
else {
// Player color selection
rgbPlay[numPlays] = getPointColor(i);
//readyPlay[numPlays] = true;
posPlay[numPlays] = i;
numPlays++;
// Display
if (numPlays > 1) {
snprintf(textBuffer, NUM_MAX_CHAR_TEXT, "%d players", numPlays);
}
else {
snprintf(textBuffer, NUM_MAX_CHAR_TEXT, "%d player", numPlays);
}
display.displayClear();
display.displayReset();
display.setTextBuffer(textBuffer);
display.setTextAlignment(PA_LEFT);
display.setSpeed(50);
display.setTextEffect(PA_SCROLL_LEFT, PA_SCROLL_LEFT);
if (numPlays >= NUM_MAX_PLAYS) {
gameState = START_LEDBATTLE;
}
}
}
}
break;
case START_LEDBATTLE:
if (first) {
first = false;
clearLeds();
clearBtnsAllEvents();
display.displayClear();
DEBUG_SERIAL.println("START_LEDBATTLE");
}
if (animStartLedBattle()) {
gameState = GAME_LEDBATTLE;
}
break;
case GAME_LEDBATTLE:
if (first) {
first = false;
clearLeds();
clearBtnsAllEvents();
display.displayClear();
display.displayReset();
display.setTextBuffer("GO!");
display.setTextAlignment(PA_CENTER);
display.setTextEffect(PA_NO_EFFECT, PA_NO_EFFECT);
// Starting random pos for players
for (int i=0; i<numPlays; i++) {
int pos = random(0, NUM_POINTS);
while (findPlayIndexInReadyPlayPos(pos) >= 0) {
pos = (pos + 1) % NUM_POINTS;
}
posPlay[i] = pos;
}
DEBUG_SERIAL.println("GAME_LEDBATTLE");
}
for (int i=0; i<NUM_POINTS; i++) {
if (btn[i].wasReleased()) {
btn[i].clearReleased();
// Increment score of hit player
int play = findPlayIndexInReadyPlayPos(i);
if (play >= 0) {
scorePlay[play] = scorePlay[play] + 5;
posPlay[play] = -1;
//DEBUG_SERIAL.print(play);
//DEBUG_SERIAL.print(":");
//DEBUG_SERIAL.println(scorePlay[play]);
// Look for new position
int pos = random(0, NUM_POINTS);
while (findPlayIndexInReadyPlayPos(pos) >= 0) {
pos = (pos + 1) % NUM_POINTS;
}
posPlay[play] = pos;
textBuffer[0] = '\0';
for (int j=0; j<numPlays; j++) {
snprintf(textBuffer + strlen(textBuffer), NUM_MAX_CHAR_TEXT - strlen(textBuffer), "P%d:%d ", j+1, scorePlay[j]);
}
display.setTextBuffer(textBuffer);
display.setTextAlignment(PA_LEFT);
display.setSpeed(50);
display.setTextEffect(PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}
}
}
if (animGameLedBattle()) {
int scoreMax = 0;
for (int j=0; j<numPlays; j++) {
if (scoreMax < scorePlay[j]) {
scoreMax = scorePlay[j];
}
}
for (int j=0; j<numPlays; j++) {
winPlay[j] = scorePlay[j] == scoreMax;
}
gameState = WINNER_LEDBATTLE;
}
break;
case WINNER_LEDBATTLE:
if (first) {
first = false;
display.displayClear();
display.displayReset();
snprintf(textBuffer + strlen(textBuffer), NUM_MAX_CHAR_TEXT - strlen(textBuffer), " - WIN ");
for (int j=0; j<numPlays; j++) {
if (winPlay[j]) {
snprintf(textBuffer + strlen(textBuffer), NUM_MAX_CHAR_TEXT - strlen(textBuffer), "P%d ", j+1);
}
}
display.setTextBuffer(textBuffer);
display.setTextAlignment(PA_LEFT);
display.setSpeed(50);
display.setTextEffect(PA_SCROLL_LEFT, PA_SCROLL_LEFT);
DEBUG_SERIAL.println("WINNER_LEDBATTLE");
}
if (animWinnerLedBattle()) {
gameState = FINISH_LEDBATTLE;
}
break;
case FINISH_LEDBATTLE:
if (first) {
first = false;
DEBUG_SERIAL.println("FINISH_LEDBATTLE");
}
break;
default:
gameState = INIT_LEDBATTLE;
break;
}
readBtns();
}
bool animInitLedBattle() {
static long timerInit = 0;
const uint16_t TIME_INIT_ANIM = 1000;
uint32_t colorCurrentLed = 0;
uint32_t colorNextLed = 0;
// Led
// Scroll color starting from 0 to NUM_POINTS skipping player colors
if ((millis() - timerInit) > TIME_INIT_ANIM) {
colorCurrentLed = ws2812b.gamma32(ws2812b.ColorHSV(random(1, 65535), 255, BRIGHTNESS));
for (int i=0; i<NUM_POINTS; i++) {
if (findPlayIndexInReadyPlayPos(i) >= 0) continue;
colorNextLed = getPointColor(i);
setPointColor(i, colorCurrentLed);
colorCurrentLed = colorNextLed;
}
ws2812b.show();
timerInit = millis();
}
return true;
}
bool animStartLedBattle() {
static long timerStart = 0;
const uint16_t TIME_START_ANIM = 1000;
static uint8_t step = 1;
if ((millis() - timerStart) > TIME_START_ANIM) {
// Display
snprintf(textBuffer, NUM_MAX_CHAR_TEXT, "%d", 5+1-step);
display.displayClear();
display.displayReset();
display.setTextBuffer(textBuffer);
display.setTextAlignment(PA_CENTER);
display.setTextEffect(PA_NO_EFFECT, PA_NO_EFFECT);
// Led
setPointColor(step-1, ws2812b.Color(255,0,0));
ws2812b.show();
step++;
if (step > 5+1) {
step = 1;
return true;
}
timerStart = millis();
}
return false;
}
bool animGameLedBattle() {
const uint16_t TIME_GAME_ANIM = 60*1000;
static long timerGame = 0;
static int posPlayOld[NUM_MAX_PLAYS] = {-1};
if (timerGame == 0) timerGame = millis();
if ((millis() - timerGame) > TIME_GAME_ANIM) {
timerGame = 0;
return true;
}
for (int i=0; i<numPlays; i++) {
if (posPlayOld[i] != posPlay[i]) {
setPointColor(posPlayOld[i], 0);
if (posPlay[i] >= 0) {
setPointColor(posPlay[i], rgbPlay[i]);
}
else {
setPointColor(posPlay[i], 0);
}
ws2812b.show();
posPlayOld[i] = posPlay[i];
}
}
return false;
}
bool animWinnerLedBattle() {
return true;
}
void setPointColor(int i, uint32_t color) {
for (int j = i*NUM_LEDS_IN_POINT; j < (i*NUM_LEDS_IN_POINT+NUM_LEDS_IN_POINT); j++) {
ws2812b.setPixelColor(j, color);
}
}
uint32_t getPointColor(int i) {
return ws2812b.getPixelColor(i*NUM_LEDS_IN_POINT);
}
int findPlayIndexInReadyPlayPos (int i) {
for (int j=0; j<NUM_MAX_PLAYS; j++) {
if ((posPlay[j] == i)) {
return j;
}
}
return -1;
}
void clearLeds() {
ws2812b.clear();
ws2812b.show();
}
void clearBtnsAllEvents() {
for (int i=0; i<NUM_POINTS; i++) {
btn[i].clearAllEvents();
}
}
void readBtns() {
for (int i=0; i<NUM_POINTS; i++) {
btn[i].readBtn();
}
}