// VERSION 0.2
// TARGET
// WOKWI ESP32 S2
// 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
// Display SSD1306
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Adafruit Neopixel
#include <Adafruit_NeoPixel.h>
// Add Internal Library
#include "BTN.h"
#include "Splash.h"
// Pin assignment
#define PIN_WS2812B 13 //Wemos Mini D7
#define PIN_BTN1 2 //Wemos Mini D4
#define PIN_BTN2 16 //Wemos Mini D3
#define PIN_SCL1 14 //Wemos Mini D5
#define PIN_SDA1 12 //Wemos Mini D6
#define PIN_SCL2 5 //Wemos Mini D1
#define PIN_SDA2 4 //Wemos Mini D2
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
// HW Constants
#define NUM_PIXELS 24 // Please EVEN number the number of LEDs (pixels) on WS2812B LED strip
#define BRIGHTNESS 255 // 255 max
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
// SW Constants
#define SNAKE_LENGTH 10
#define POS_LED_BTN1 11
#define POS_LED_BTN2 23
// HW Global variables
Adafruit_NeoPixel ws2812b(NUM_PIXELS, PIN_WS2812B, NEO_GRB + NEO_KHZ800);
MyBtn btn1(PIN_BTN1);
MyBtn btn2(PIN_BTN2);
TwoWire I2Cone = TwoWire(0);
TwoWire I2Ctwo = TwoWire(1);
Adafruit_SSD1306 display1(SCREEN_WIDTH, SCREEN_HEIGHT, &I2Cone, OLED_RESET);
Adafruit_SSD1306 display2(SCREEN_WIDTH, SCREEN_HEIGHT, &I2Ctwo, OLED_RESET);
// SW Global variables
enum gameStateEnum {INIT_LEDCATCH, START_LEDCATCH, GAME_LEDCATCH, WINNER_LEDCATCH, FINISH_LEDCATCH, INIT_LEDSNAKE, START_LEDSNAKE, GAME_LEDSNAKE, WINNER_LEDSNAKE, FINISH_LEDSNAKE};
enum gameStateEnum gameState = INIT_LEDCATCH;
enum gameStateEnum gameStateOld = START_LEDCATCH;
bool first = false;
uint32_t rgbPlay1 = 0; // Color of player1
uint32_t rgbPlay2 = 0; // Color of player2
bool readyPlay1 = false; // Player1 ready to play
bool readyPlay2 = false; // Player2 ready to play
const uint16_t initHuePlay1 = 0; // red
const uint16_t initHuePlay2 = 21845; // green
bool rotDirPlay1 = true;
bool rotDirPlay2 = true;
uint8_t sizePlay1 = 3;
uint8_t sizePlay2 = 3;
uint8_t posPlay1 = 0;
uint8_t posPlay2 = 0;
uint32_t snakeColors[SNAKE_LENGTH];
bool winPlay1, winPlay2 = false; // true when the relative player won
bool temptPlay1, temptPlay2 = false;
uint16_t scorePlay1, scorePlay2 = 0;
uint8_t shotPlay1, shotPlay2 = 0;
bool changeColorPlay1, changeColorPlay2 = false;
void displayText(uint8_t player, char* text, int n = 0) {
char line[10];
int16_t x1, y1;
uint16_t w, h;
snprintf(line, 10, text, n);
if (player == 1) {
//Wire.begin(PIN_SDA1, PIN_SCL1);
display1.clearDisplay();
display1.setTextSize(2);
display1.setTextColor(WHITE); // Draw white text
display1.getTextBounds(line, 0, 0, &x1, &y1, &w, &h); //calc width of new string
display1.setCursor((SCREEN_WIDTH - w) / 2, (SCREEN_HEIGHT - h) / 2);
display1.print(line);
display1.display();
}
else {
//Wire.begin(PIN_SDA2, PIN_SCL2);
display2.clearDisplay();
display2.setTextSize(2);
display2.setTextColor(WHITE); // Draw white text
display2.getTextBounds(line, 0, 0, &x1, &y1, &w, &h); //calc width of new string
display2.setCursor((SCREEN_WIDTH - w) / 2, (SCREEN_HEIGHT - h) / 2);
display2.print(line);
display2.display();
}
}
void setup() {
Serial.begin(9600);
DEBUG_SERIAL.println("SETUP");
ws2812b.begin();
pinMode(PIN_BTN1, INPUT_PULLUP);
pinMode(PIN_BTN2, INPUT_PULLUP);
I2Cone.begin(PIN_SDA1, PIN_SCL1, 100000);
I2Ctwo.begin(PIN_SDA2, PIN_SCL2, 100000);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
//Wire.begin(PIN_SDA1, PIN_SCL1);
if(!display1.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
//Wire.begin(PIN_SDA2, PIN_SCL2);
if(!display2.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
//Wire.begin(PIN_SDA1, PIN_SCL1);
display1.clearDisplay();
//Wire.begin(PIN_SDA2, PIN_SCL2);
display2.clearDisplay();
randomSeed(analogRead(0));
}
void loop() {
// First evaluation for first time in new state
if (gameStateOld != gameState) {
first = true;
gameStateOld = gameState;
}
switch (gameState) {
case INIT_LEDCATCH:
if (first) {
first = false;
clearLeds();
btn1.clearAllEvents();
btn2.clearAllEvents();
readyPlay1 = readyPlay2 = false;
sizePlay1 = 3;
sizePlay2 = 3;
posPlay1 = random((POS_LED_BTN2+1)%NUM_PIXELS, POS_LED_BTN1-sizePlay1+1);
posPlay2 = random((POS_LED_BTN1+1)%NUM_PIXELS, POS_LED_BTN2-sizePlay2+1);
rotDirPlay1 = true;
rotDirPlay2 = true;
//Wire.begin(PIN_SDA1, PIN_SCL1);
display1.clearDisplay();
display1.drawBitmap(0, 0, epd_bitmap_Splash_TugOfWar, 128, 32, 1);
display1.display();
//Wire.begin(PIN_SDA2, PIN_SCL2);
display2.clearDisplay();
display2.drawBitmap(0, 0, epd_bitmap_Splash_TugOfWar, 128, 32, 1);
display2.display();
DEBUG_SERIAL.println("INIT_LEDCATCH");
}
animInitLedCatch();
if (btn1.wasReleased()) {
btn1.clearReleased();
rgbPlay1 = ws2812b.getPixelColor(POS_LED_BTN1);
readyPlay1 = true;
displayText(1, "Ready");
}
if (btn2.wasReleased()) {
btn2.clearReleased();
rgbPlay2 = ws2812b.getPixelColor(POS_LED_BTN2);
readyPlay2 = true;
displayText(2, "Ready");
}
if (readyPlay1 && readyPlay2) {
gameState = START_LEDCATCH;
}
if (btn1.wasLongPressed() && btn2.wasLongPressed()) {
btn1.clearLongPressed();
btn2.clearLongPressed();
gameState = INIT_LEDSNAKE;
}
break;
case START_LEDCATCH:
if (first) {
first = false;
clearLeds();
btn1.clearAllEvents();
btn2.clearAllEvents();
DEBUG_SERIAL.println("START_LEDCATCH");
}
if (animStartLedCatch()) {
gameState = GAME_LEDCATCH;
}
break;
case GAME_LEDCATCH:
if (first) {
first = false;
clearLeds();
btn1.clearAllEvents();
btn2.clearAllEvents();
sizePlay1 = 3;
sizePlay2 = 3;
posPlay1 = random((POS_LED_BTN2+1)%NUM_PIXELS, POS_LED_BTN1-sizePlay1+1);
posPlay2 = random((POS_LED_BTN1+1)%NUM_PIXELS, POS_LED_BTN2-sizePlay2+1);
rotDirPlay1 = true;
rotDirPlay2 = true;
winPlay1 = winPlay2 = false;
displayText(1, "GO !");
displayText(2, "GO !");
DEBUG_SERIAL.println("GAME_LEDCATCH");
}
if (btn1.wasPressed()) {
btn1.clearPressed();
if (!temptPlay1) {
temptPlay1 = true;
// If pixel contains color of play1
if ((ws2812b.getPixelColor(POS_LED_BTN1) & rgbPlay1) == rgbPlay1) {
scorePlay1 = scorePlay1 + 5;
// New size, pos and rot
if (scorePlay1 < 60) {
sizePlay1 = ceil(3.0-((3.0-1.0)/60.0)*scorePlay1);
}
else {
sizePlay1 = 1;
}
posPlay1 = random((POS_LED_BTN2+1)%NUM_PIXELS, POS_LED_BTN1-sizePlay1+1);
rotDirPlay1 = true;
}
else {
// If pixel contains color of play2
if ((ws2812b.getPixelColor(POS_LED_BTN1) & rgbPlay2) == rgbPlay2) {
rotDirPlay2 = !rotDirPlay2;
}
else {
if (scorePlay1 > 0) scorePlay1--;
}
}
displayText(1, "%d", (int)(scorePlay1));
}
}
if (btn2.wasPressed()) {
btn2.clearPressed();
if (!temptPlay2) {
temptPlay2 = true;
// If pixel contains color of play2
if ((ws2812b.getPixelColor(POS_LED_BTN2) & rgbPlay2) == rgbPlay2) {
scorePlay2 = scorePlay2 + 5;
// New size, pos and rot
if (scorePlay2 < 60) {
sizePlay2 = ceil(3.0-((3.0-1.0)/60.0)*scorePlay2);
}
else {
sizePlay2 = 1;
}
posPlay2 = random((POS_LED_BTN1+1)%NUM_PIXELS, POS_LED_BTN2-sizePlay2+1);
rotDirPlay2 = true;
}
else {
// If pixel contains color of play1
if ((ws2812b.getPixelColor(POS_LED_BTN2) & rgbPlay1) == rgbPlay1) {
rotDirPlay1 = !rotDirPlay1;
}
else {
if (scorePlay2 > 0) scorePlay2--;
}
}
displayText(2, "%d", (int)(scorePlay2));
}
}
if (animGameLedCatch()) {
winPlay1 = scorePlay1 >= scorePlay2;
winPlay2 = scorePlay2 >= scorePlay1;
gameState = WINNER_LEDSNAKE;
}
break;
case WINNER_LEDCATCH:
if (first) {
first = false;
clearLeds();
btn1.clearAllEvents();
btn2.clearAllEvents();
if (winPlay1) {
displayText(1, "WIN: %d", scorePlay1);
}
else {
displayText(1, "LOSE: %d", scorePlay1);
}
if (winPlay2) {
displayText(2, "WIN: %d", scorePlay2);
}
else {
displayText(2, "LOSE: %d", scorePlay2);
}
DEBUG_SERIAL.println("WINNER_LEDCATCH");
}
if (animWinnerLedCatch()) {
gameState = FINISH_LEDCATCH;
}
break;
case FINISH_LEDCATCH:
if (first) {
first = false;
ws2812b.fill(winPlay1 ? rgbPlay1 : rgbPlay2);
ws2812b.show();
btn1.clearAllEvents();
btn2.clearAllEvents();
DEBUG_SERIAL.println("FINISH_LEDCATCH");
}
if (btn1.wasReleased() || btn2.wasReleased()) {
btn1.clearReleased();
btn2.clearReleased();
gameState = INIT_LEDCATCH;
}
break;
case INIT_LEDSNAKE:
if (first) {
first = false;
clearLeds();
btn1.clearAllEvents();
btn2.clearAllEvents();
readyPlay1 = readyPlay2 = false;
posPlay1 = POS_LED_BTN1 + 1;
rotDirPlay1 = true;
for (int i=0; i<SNAKE_LENGTH; i++) {
if (i<(SNAKE_LENGTH/2))
snakeColors[i] = ws2812b.gamma32(ws2812b.ColorHSV(initHuePlay1, 255, BRIGHTNESS));
else
snakeColors[i] = ws2812b.gamma32(ws2812b.ColorHSV(initHuePlay2, 255, BRIGHTNESS));
}
//Wire.begin(PIN_SDA1, PIN_SCL1);
display1.clearDisplay();
display1.drawBitmap(0, 0, epd_bitmap_Splash_LedFall, 128, 32, 1);
display1.display();
//Wire.begin(PIN_SDA2, PIN_SCL2);
display2.clearDisplay();
display2.drawBitmap(0, 0, epd_bitmap_Splash_LedFall, 128, 32, 1);
display2.display();
DEBUG_SERIAL.println("INIT_LEDSNAKE");
}
animInitLedSnake();
if (btn1.wasReleased()) {
btn1.clearReleased();
rgbPlay1 = ws2812b.getPixelColor(POS_LED_BTN1);
readyPlay1 = true;
displayText(1, "Ready");
}
if (btn2.wasReleased()) {
btn2.clearReleased();
rgbPlay2 = ws2812b.getPixelColor(POS_LED_BTN2);
readyPlay2 = true;
displayText(2, "Ready");
}
if (readyPlay1 && readyPlay2) {
gameState = START_LEDSNAKE;
}
if (btn1.wasLongPressed() && btn2.wasLongPressed()) {
btn1.clearLongPressed();
btn2.clearLongPressed();
gameState = INIT_LEDCATCH;
}
break;
case START_LEDSNAKE:
if (first) {
first = false;
clearLeds();
btn1.clearAllEvents();
btn2.clearAllEvents();
DEBUG_SERIAL.println("START_LEDSNAKE");
}
if (animStartLedSnake()) {
gameState = GAME_LEDSNAKE;
}
break;
case GAME_LEDSNAKE:
if (first) {
first = false;
clearLeds();
btn1.clearAllEvents();
btn2.clearAllEvents();
posPlay1 = random(0, NUM_PIXELS);
rotDirPlay1 = true;
for (int i=0; i<SNAKE_LENGTH; i++) {
if (i<(SNAKE_LENGTH/2))
snakeColors[i] = rgbPlay1;
else
snakeColors[i] = rgbPlay2;
}
scorePlay1 = scorePlay2 = 5;
shotPlay1 = shotPlay1 = 0;
winPlay1 = winPlay2 = false;
displayText(1, "GO !");
displayText(2, "GO !");
DEBUG_SERIAL.println("GAME_LEDSNAKE");
}
if (btn1.wasPressed()) {
btn1.clearPressed();
if (!temptPlay1) {
temptPlay1 = true;
if (isIn(posPlay1, SNAKE_LENGTH, POS_LED_BTN1)) {
if (snakeColors[POS_LED_BTN1-posPlay1] == rgbPlay1) {
rotDirPlay1 = !rotDirPlay1;
changeColorPlay1 = false;
}
else {
scorePlay1--;
scorePlay2++;
snakeColors[POS_LED_BTN1-posPlay1] = rgbPlay1;
changeColorPlay1 = true;
}
if ((shotPlay1 & 0x01) > 0) {
if (((POS_LED_BTN1-posPlay1-1)<0) || (ws2812b.getPixelColor(POS_LED_BTN1 - 1) == rgbPlay1)) {
shotPlay1 = shotPlay1 & 0xFE;
}
else {
scorePlay1--;
scorePlay2++;
snakeColors[POS_LED_BTN1-posPlay1-1] = rgbPlay1;
}
}
else {
if (changeColorPlay1 && (ws2812b.getPixelColor(POS_LED_BTN1 - 1) == rgbPlay1)) {
shotPlay1 = shotPlay1 | 0x01;
}
}
if ((shotPlay1 & 0x02) > 0) {
if (((POS_LED_BTN1-posPlay1+1)>=SNAKE_LENGTH) || (ws2812b.getPixelColor(POS_LED_BTN1 + 1) == rgbPlay1)) {
shotPlay1 = shotPlay1 & 0xFD;
}
else {
scorePlay1--;
scorePlay2++;
snakeColors[POS_LED_BTN1-posPlay1+1] = rgbPlay1;
}
}
else {
if (changeColorPlay1 && (ws2812b.getPixelColor(POS_LED_BTN1 + 1) == rgbPlay1)) {
shotPlay1 = shotPlay1 | 0x02;
}
}
// TODO funziona solo con POS_LED_BTN1 > 0
ws2812b.setPixelColor(POS_LED_BTN1, snakeColors[POS_LED_BTN1-posPlay1]);
if ((POS_LED_BTN1-posPlay1-1)<0)
ws2812b.setPixelColor(POS_LED_BTN1-1, snakeColors[POS_LED_BTN1-posPlay1-1]);
if ((POS_LED_BTN1-posPlay1+1)>=SNAKE_LENGTH)
ws2812b.setPixelColor(POS_LED_BTN1+1, snakeColors[POS_LED_BTN1-posPlay1+1]);
ws2812b.show();
displayText(1, "%d", (int)(shotPlay1));
//displayText(2, "%d", (int)(scorePlay2));
}
}
}
if (btn2.wasPressed() && !temptPlay2) {
btn2.clearPressed();
if (!temptPlay2) {
temptPlay2 = true;
if (isIn(posPlay1, SNAKE_LENGTH, POS_LED_BTN2)) {
if (snakeColors[POS_LED_BTN2-posPlay1] == rgbPlay2) {
rotDirPlay1 = !rotDirPlay1;
changeColorPlay2 = false;
}
else {
scorePlay2--;
scorePlay1++;
snakeColors[POS_LED_BTN2-posPlay1] = rgbPlay2;
changeColorPlay2 = true;
}
if ((shotPlay2 & 0x01) > 0) {
if (((POS_LED_BTN2-posPlay1-1)<0) || (ws2812b.getPixelColor(POS_LED_BTN2 - 1) == rgbPlay2)) {
shotPlay2 = shotPlay2 & 0xFE;
}
else {
scorePlay2--;
scorePlay1++;
snakeColors[POS_LED_BTN2-posPlay1-1] = rgbPlay2;
}
}
else {
if (changeColorPlay2 && (ws2812b.getPixelColor(POS_LED_BTN2 - 1) == rgbPlay2)) {
shotPlay2 = shotPlay2 | 0x01;
}
}
if ((shotPlay2 & 0x02) > 0) {
if (((POS_LED_BTN2-posPlay1+1)>=SNAKE_LENGTH) || (ws2812b.getPixelColor(0 /* POS_LED_BTN2+1 */) == rgbPlay2)) {
shotPlay2 = shotPlay2 & 0xFD;
}
else {
scorePlay2--;
scorePlay1++;
snakeColors[POS_LED_BTN2-posPlay1+1] = rgbPlay2;
}
}
else {
if (changeColorPlay2 && (ws2812b.getPixelColor(0 /* POS_LED_BTN2+1 */) == rgbPlay2)) {
shotPlay2 = shotPlay2 | 0x02;
}
}
// TODO funziona solo con POS_LED_BTN2 > 0
ws2812b.setPixelColor(POS_LED_BTN2, rgbPlay2);
if ((POS_LED_BTN2-posPlay1-1)<0)
ws2812b.setPixelColor(POS_LED_BTN2-1, snakeColors[POS_LED_BTN2-posPlay1-1]);
if ((POS_LED_BTN2-posPlay1+1)>=SNAKE_LENGTH)
ws2812b.setPixelColor(POS_LED_BTN2+1, snakeColors[POS_LED_BTN2-posPlay1+1]);
ws2812b.show();
displayText(2, "%d", (int)(shotPlay2));
//displayText(1, "%d", (int)(scorePlay1));
}
}
}
animGameLedSnake();
if ((scorePlay1 == 0) || (scorePlay2 == 0)) {
winPlay1 = scorePlay1 <= scorePlay2;
winPlay2 = scorePlay2 <= scorePlay1;
gameState = WINNER_LEDSNAKE;
}
break;
case WINNER_LEDSNAKE:
if (first) {
first = false;
clearLeds();
btn1.clearAllEvents();
btn2.clearAllEvents();
if (winPlay1) {
displayText(1, "WIN: %d", scorePlay1);
}
else {
displayText(1, "LOSE: %d", scorePlay1);
}
if (winPlay2) {
displayText(2, "WIN: %d", scorePlay2);
}
else {
displayText(2, "LOSE: %d", scorePlay2);
}
DEBUG_SERIAL.println("WINNER_LEDSNAKE");
}
if (animWinnerLedSnake()) {
gameState = FINISH_LEDSNAKE;
}
break;
case FINISH_LEDSNAKE:
if (first) {
first = false;
ws2812b.fill(winPlay1 ? rgbPlay1 : rgbPlay2);
ws2812b.show();
btn1.clearAllEvents();
btn2.clearAllEvents();
DEBUG_SERIAL.println("FINISH_LEDSNAKE");
}
if (btn1.wasReleased() || btn2.wasReleased()) {
btn1.clearReleased();
btn2.clearReleased();
gameState = INIT_LEDSNAKE;
}
break;
default:
gameState = INIT_LEDCATCH;
break;
}
btn1.readBtn();
btn2.readBtn();
}
bool animInitLedSnake() {
static long timerInitFade = 0;
const uint8_t TIME_INIT_FADE_ANIM = 10;
static long timerInit = 0;
const uint16_t TIME_INIT_ANIM = 500;
static uint16_t hue1 = 0; // green
static uint16_t hue2 = 21845; // red
const uint8_t PROB_CATCH = 10;
if ((millis() - timerInitFade) > TIME_INIT_FADE_ANIM) {
if (!readyPlay1) {
hue1 += random(80, 121);
ws2812b.setPixelColor(POS_LED_BTN1, ws2812b.gamma32(ws2812b.ColorHSV(hue1, 255, BRIGHTNESS)));
}
if (!readyPlay2) {
hue2 += random(80, 121);
ws2812b.setPixelColor(POS_LED_BTN2, ws2812b.gamma32(ws2812b.ColorHSV(hue2, 255, BRIGHTNESS)));
}
ws2812b.show();
timerInitFade = millis();
}
if ((millis() - timerInit) > TIME_INIT_ANIM) {
if (rotDirPlay1) {
posPlay1 = (posPlay1+1)%NUM_PIXELS;
}
else {
posPlay1 = (posPlay1>1) ? posPlay1-1 : NUM_PIXELS;
}
ws2812b.clear();
ws2812b.setPixelColor(POS_LED_BTN1, ws2812b.gamma32(ws2812b.ColorHSV(hue1, 255, BRIGHTNESS)));
ws2812b.setPixelColor(POS_LED_BTN2, ws2812b.gamma32(ws2812b.ColorHSV(hue2, 255, BRIGHTNESS)));
for (int i=0; i<SNAKE_LENGTH; i++) {
if (((posPlay1+i)%NUM_PIXELS != POS_LED_BTN1) && ((posPlay1+i)%NUM_PIXELS != POS_LED_BTN2)) {
ws2812b.setPixelColor((posPlay1+i)%NUM_PIXELS, snakeColors[i]);
}
}
ws2812b.show();
if (isIn(posPlay1, SNAKE_LENGTH, POS_LED_BTN1)) {
if ((random(1, 100) < PROB_CATCH) && !(temptPlay1)) {
temptPlay1 = true;
if (snakeColors[POS_LED_BTN1-posPlay1] == ws2812b.gamma32(ws2812b.ColorHSV(initHuePlay1, 255, BRIGHTNESS)))
rotDirPlay1 = !rotDirPlay1;
else
snakeColors[POS_LED_BTN1-posPlay1] = ws2812b.gamma32(ws2812b.ColorHSV(initHuePlay1, 255, BRIGHTNESS));
}
}
else {
temptPlay1 = false;
}
if (isIn(posPlay1, SNAKE_LENGTH, POS_LED_BTN2)) {
if ((random(1, 100) < PROB_CATCH) && !(temptPlay2)) {
temptPlay2 = true;
if (snakeColors[POS_LED_BTN2-posPlay1] == ws2812b.gamma32(ws2812b.ColorHSV(initHuePlay2, 255, BRIGHTNESS)))
rotDirPlay1 = !rotDirPlay1;
else
snakeColors[POS_LED_BTN2-posPlay1] = ws2812b.gamma32(ws2812b.ColorHSV(initHuePlay2, 255, BRIGHTNESS));
}
}
else {
temptPlay2 = false;
}
timerInit = millis();
}
return true;
}
bool animGameLedSnake() {
const uint16_t TIME_GAME_ANIM = 60*1000;
static long timerGame = 0;
const uint16_t TIME_SNAKE_START_ANIM = 1000;
const uint16_t TIME_SNAKE_END_ANIM = 100;
static long timerSnake = 0;
static uint16_t timeSnakeAnim = 1000;
if (timerGame == 0) timerGame = millis();
if ((millis() - timerGame) > TIME_GAME_ANIM) {
timerGame = 0;
return true;
}
if (temptPlay1) {
if (!(isIn(posPlay1, SNAKE_LENGTH, POS_LED_BTN1))) {
temptPlay1 = false;
}
}
if (temptPlay2) {
if (!(isIn(posPlay1, SNAKE_LENGTH, POS_LED_BTN2))) {
temptPlay2 = false;
}
}
if ((millis() - timerSnake) > timeSnakeAnim) {
if (rotDirPlay1) {
posPlay1 = (posPlay1+1)%NUM_PIXELS;
}
else {
posPlay1 = (posPlay1>1) ? posPlay1-1 : NUM_PIXELS;
}
ws2812b.clear();
for (int i=0; i<SNAKE_LENGTH; i++) {
ws2812b.setPixelColor((posPlay1+i)%NUM_PIXELS, snakeColors[i]);
}
ws2812b.show();
timeSnakeAnim = TIME_SNAKE_START_ANIM - ((TIME_SNAKE_START_ANIM-TIME_SNAKE_END_ANIM) * ((float)(millis() - timerGame) / (float)TIME_GAME_ANIM));
timerSnake = millis();
}
return false;
}
bool animWinnerLedSnake() {
return animWinnerLedCatch();
}
bool animStartLedSnake() {
return animStartLedCatch();
}
bool animInitLedCatch() {
static long timerInitFade = 0;
const uint8_t TIME_INIT_FADE_ANIM = 10;
static long timerInit = 0;
const uint16_t TIME_INIT_ANIM = 500;
static uint16_t hue1 = 0; // red
static uint16_t hue2 = 21845; // green
static bool dec1 = false;
static bool dec2 = false;
const uint16_t HUE_MIN_DIST = 15000;
uint16_t minHue = 0;
uint16_t maxHue = 0;
const uint8_t PROB_CATCH = 50;
const uint16_t initHuePlay1 = 0; // red
const uint16_t initHuePlay2 = 21845; // green
uint32_t color;
// fade colors for players
if ((millis() - timerInitFade) > TIME_INIT_FADE_ANIM) {
ws2812b.clear();
// draw play1 leds
color = ws2812b.gamma32(ws2812b.ColorHSV(initHuePlay1, 255, BRIGHTNESS));
for (int i=0; i<sizePlay1; i++) {
ws2812b.setPixelColor((posPlay1+i)%NUM_PIXELS, color);
}
// draw play2 leds
for (int i=0; i<sizePlay2; i++) {
color = ws2812b.getPixelColor((posPlay2+i)%NUM_PIXELS);
color |= ws2812b.gamma32(ws2812b.ColorHSV(initHuePlay2, 255, BRIGHTNESS));
ws2812b.setPixelColor((posPlay2+i)%NUM_PIXELS, color);
}
if (!readyPlay1) {
minHue = hue2 - HUE_MIN_DIST;
maxHue = hue2 + HUE_MIN_DIST;
if (dec1) {
hue1 -= 100;
if ((hue1 >= minHue) && (hue1 <= maxHue)) {
dec1 = !dec1;
hue1 = maxHue;
DEBUG_SERIAL.println("1+");
}
}
else {
hue1 += 100;
if ((hue1 >= minHue) && (hue1 <= maxHue)) {
dec1 = !dec1;
hue1 = minHue;
}
}
ws2812b.setPixelColor(POS_LED_BTN1, ws2812b.gamma32(ws2812b.ColorHSV(hue1, 255, BRIGHTNESS)));
}
else {
ws2812b.setPixelColor(POS_LED_BTN1, rgbPlay1);
}
if (!readyPlay2) {
minHue = hue1 - HUE_MIN_DIST;
maxHue = hue1 + HUE_MIN_DIST;
if (dec2) {
hue2 -= 100;
if (minHue<maxHue) {
if ((hue2 >= minHue) && (hue2 <= maxHue)) {
dec2 = !dec2;
hue2 = maxHue;
}
}
else {
if ((hue2 >= minHue) || (hue2 <= maxHue)) {
dec2 = !dec2;
hue2 = maxHue;
}
}
}
else {
hue2 += 100;
if (minHue<maxHue) {
if ((hue2 >= minHue) && (hue2 <= maxHue)) {
dec2 = !dec2;
hue2 = minHue;
}
}
else {
if ((hue2 >= minHue) || (hue2 <= maxHue)) {
dec2 = !dec2;
hue2 = minHue;
}
}
}
ws2812b.setPixelColor(POS_LED_BTN2, ws2812b.gamma32(ws2812b.ColorHSV(hue2, 255, BRIGHTNESS)));
}
else {
ws2812b.setPixelColor(POS_LED_BTN2, rgbPlay2);
}
ws2812b.show();
timerInitFade = millis();
}
// random leds rounding
if ((millis() - timerInit) > TIME_INIT_ANIM) {
// play1 emulate catch own color
if (isIn(posPlay1, sizePlay1, POS_LED_BTN1)) {
if (random(1, 100) < PROB_CATCH) {
posPlay1 = random((POS_LED_BTN2+1)%NUM_PIXELS, POS_LED_BTN1-sizePlay1+1);
rotDirPlay1 = true;
sizePlay1 = random(1, 4);
}
}
// play1 emulate catch other color
if (isIn(posPlay2, sizePlay2, POS_LED_BTN1)) {
if (random(1, 100) < PROB_CATCH) {
rotDirPlay2 = !rotDirPlay2;
}
}
// play2 emulate catch own color
if (isIn(posPlay2, sizePlay2, POS_LED_BTN2)) {
if (random(1, 100) < PROB_CATCH) {
posPlay2 = random((POS_LED_BTN1+1)%NUM_PIXELS, POS_LED_BTN2-sizePlay2+1);
rotDirPlay2 = true;
sizePlay2 = random(1, 4);
}
}
// play2 emulate catch other color
if (isIn(posPlay1, sizePlay1, POS_LED_BTN2)) {
if (random(1, 100) < PROB_CATCH) {
rotDirPlay1 = !rotDirPlay1;
}
}
// New posPlay1
if (rotDirPlay1) {
posPlay1 = (posPlay1+1)%NUM_PIXELS;
}
else {
posPlay1 = (posPlay1>1) ? posPlay1-1 : NUM_PIXELS;
}
// New posPlay2
if (rotDirPlay2) {
posPlay2 = (posPlay2+1)%NUM_PIXELS;
}
else {
posPlay2 = (posPlay2>1) ? posPlay2-1 : NUM_PIXELS;
}
timerInit = millis();
}
return true;
}
bool animStartLedCatch() {
static long timerStart = 0;
const uint16_t TIME_START_ANIM = 1000;
static uint8_t step = 1;
if ((millis() - timerStart) > TIME_START_ANIM) {
// first step turn on 5 led
if (step == 1) {
for (int i=POS_LED_BTN1-5-2;i<POS_LED_BTN1-2;i++) {
ws2812b.setPixelColor(i, rgbPlay1);
}
for (int i=POS_LED_BTN2-5-2;i<POS_LED_BTN2-2;i++) {
ws2812b.setPixelColor(i, rgbPlay2);
}
}
else {
ws2812b.setPixelColor(POS_LED_BTN1-5+step-2-2, 0);
ws2812b.setPixelColor(POS_LED_BTN2-5+step-2-2, 0);
}
ws2812b.show();
displayText(1, "%d", 6-step);
displayText(2, "%d", 6-step);
if (step > 5) {
step = 1;
return true;
}
step++;
timerStart = millis();
}
return false;
}
bool animGameLedCatch() {
const uint16_t TIME_GAME_ANIM = 60*1000;
static long timerGame = 0;
const uint16_t TIME_NEW_LED_START_ANIM = 1000;
const uint16_t TIME_NEW_LED_END_ANIM = 100;
static long timerNewLed = 0;
static uint16_t timeNewLedAnim = 1000;
static long timerBlinkPlay1 = 0;
static long timerBlinkPlay2 = 0;
static uint8_t statusBlinkPlay1 = 0;
static uint8_t statusBlinkPlay2 = 0;
static uint32_t oldColorPlay1 = 0;
static uint32_t oldColorPlay2 = 0;
const uint16_t TIME_BLINK_ANIM = 20;
uint8_t r, g, b;
uint32_t color;
if (timerGame == 0) timerGame = millis();
if ((millis() - timerGame) > TIME_GAME_ANIM) {
timerGame = 0;
return true;
}
if (temptPlay1) {
switch (statusBlinkPlay1) {
case 0: // OFF
oldColorPlay1 = ws2812b.getPixelColor(POS_LED_BTN1);
timerBlinkPlay1 = millis();
ws2812b.setPixelColor(POS_LED_BTN1, 0);
ws2812b.setPixelColor(POS_LED_BTN1-1, oldColorPlay1);
ws2812b.setPixelColor(POS_LED_BTN1+1, oldColorPlay1);
ws2812b.show();
statusBlinkPlay1++;
break;
case 1: // ON
if ((millis() - timerBlinkPlay1) > TIME_BLINK_ANIM) {
timerBlinkPlay1 = millis();
ws2812b.setPixelColor(POS_LED_BTN1-1, 0);
ws2812b.setPixelColor(POS_LED_BTN1+1, 0);
ws2812b.setPixelColor(POS_LED_BTN1-2, oldColorPlay1);
ws2812b.setPixelColor(POS_LED_BTN1+2, oldColorPlay1);
ws2812b.show();
statusBlinkPlay1++;
}
break;
case 2: // Finish
if ((millis() - timerBlinkPlay1) > TIME_BLINK_ANIM) {
timerBlinkPlay1 = millis();
ws2812b.setPixelColor(POS_LED_BTN1-1, 0);
ws2812b.setPixelColor(POS_LED_BTN1+1, 0);
r = (oldColorPlay1 >> 16) & 0xFF;
g = (oldColorPlay1 >> 8) & 0xFF;
b = (oldColorPlay1) & 0xFF;
ws2812b.setPixelColor(POS_LED_BTN1-2, r/2, g/2, b/2);
ws2812b.setPixelColor(POS_LED_BTN1+2, r/2, g/2, b/2);
ws2812b.show();
statusBlinkPlay1++;
}
break;
case 3: // Finish
if ((millis() - timerBlinkPlay1) > TIME_BLINK_ANIM) {
timerBlinkPlay1 = millis();
ws2812b.setPixelColor(POS_LED_BTN1-2, 0);
ws2812b.setPixelColor(POS_LED_BTN1+2, 0);
ws2812b.show();
statusBlinkPlay1++;
}
break;
case 4: // Finish
// Do nothing only wait
break;
default:
statusBlinkPlay1 = 0;
break;
}
}
else {
statusBlinkPlay1 = 0;
}
if (temptPlay2) {
switch (statusBlinkPlay2) {
case 0: // OFF
oldColorPlay2 = ws2812b.getPixelColor(POS_LED_BTN2);
timerBlinkPlay2 = millis();
ws2812b.setPixelColor(POS_LED_BTN2, 0);
ws2812b.show();
statusBlinkPlay2++;
break;
case 1: // ON
if ((millis() - timerBlinkPlay2) > TIME_BLINK_ANIM) {
timerBlinkPlay2 = millis();
ws2812b.setPixelColor(POS_LED_BTN2, oldColorPlay2);
ws2812b.show();
statusBlinkPlay2++;
}
break;
case 2: // Finish
// Do nothing only wait
default:
statusBlinkPlay2 = 0;
}
}
else {
statusBlinkPlay2 = 0;
}
if ((millis() - timerNewLed) > timeNewLedAnim) {
// Scrolling led toward play1
temptPlay1 = false;
// New posPlay1
if (rotDirPlay1) {
posPlay1 = (posPlay1+1)%NUM_PIXELS;
}
else {
posPlay1 = (posPlay1>1) ? posPlay1-1 : NUM_PIXELS;
}
// Scrolling led toward play2
temptPlay2 = false;
// New posPlay2
if (rotDirPlay2) {
posPlay2 = (posPlay2+1)%NUM_PIXELS;
}
else {
posPlay2 = (posPlay2>1) ? posPlay2-1 : NUM_PIXELS;
}
ws2812b.clear();
// draw play1 leds
for (int i=0; i<sizePlay1; i++) {
ws2812b.setPixelColor((posPlay1+i)%NUM_PIXELS, rgbPlay1);
}
// draw play2 leds
for (int i=0; i<sizePlay2; i++) {
color = ws2812b.getPixelColor((posPlay2+i)%NUM_PIXELS);
color |= rgbPlay2;
ws2812b.setPixelColor((posPlay2+i)%NUM_PIXELS, color);
}
ws2812b.show();
timeNewLedAnim = TIME_NEW_LED_START_ANIM - ((TIME_NEW_LED_START_ANIM-TIME_NEW_LED_END_ANIM) * ((float)(millis() - timerGame) / (float)TIME_GAME_ANIM));
timerNewLed = millis();
}
return false;
}
bool animWinnerLedCatch() {
static long timerWinner = 0;
const uint16_t TIME_WINNER_ANIM = 500;
const uint8_t NUM_BLINK = 5;
static uint32_t color = 0;
static uint8_t numBlink = 0;
if ((millis() - timerWinner) > TIME_WINNER_ANIM) {
if (color == 0) {
color = winPlay1 ? rgbPlay1 : rgbPlay2;
}
else {
color = 0;
numBlink++;
}
ws2812b.fill(color);
ws2812b.show();
if (numBlink >= NUM_BLINK) {
numBlink = 0;
return true;
}
timerWinner = millis();
}
return false;
}
bool isIn(uint8_t pos, uint8_t size, uint8_t target) {
for (int i=0;i<size;i++) {
if (((pos+i)%NUM_PIXELS) == target) {
return true;
}
}
return false;
}
void clearLeds() {
ws2812b.clear();
ws2812b.show();
}