propwing-clicks-and-modes.ino 57.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
/* Wand Ring Prototype Dueling Code
(c) 2018, 2019, Jonathan A. Leistiko
contains:
• libraries and modified code from Adafruit
• FastLED code by "kreigsman" - https://gist.github.com/kriegsman/062e10f7f07ba8518af6 */

///////////////////////// Accelerometer Libraries
#include <Wire.h>
#include <SPI.h> // If using the PropMaker Wing
#include <Adafruit_LIS3DH.h> // If using the PropMaker Wing
// #include <Adafruit_MMA8451.h> // If using the seperate MMA8451
#include <Adafruit_Sensor.h>

// Used for software SPI
#define LIS3DH_CLK 13
#define LIS3DH_MISO 12
#define LIS3DH_MOSI 11
// Used for hardware & software SPI
#define LIS3DH_CS 10
// software SPI
Adafruit_LIS3DH lis = Adafruit_LIS3DH(); // Instantiate access to the Accelerometer

///////////////////////// LED (NeoPixel) Driver Libraries
#include "FastLED.h"

// LED pinout(s)
#define DATA_PIN    14

// Other FASTLED Variables
#define LED_TYPE    WS2811
#define COLOR_ORDER GRB
#define NUM_LEDS    30
CRGB leds[NUM_LEDS];

#define BRIGHTNESS          60
#define FRAMES_PER_SECOND  120

// -- The core to run FastLED.show()
#define FASTLED_SHOW_CORE 0

// -- Task handles for use in the notifications
static TaskHandle_t FastLEDshowTaskHandle = 0;
static TaskHandle_t userTaskHandle = 0;
// END LED Variables

46 47 48
int octave = 5; // Set the octave for notes / audio

///////////////////////// Wand Variables
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
// The wand button controls what mode the wand is in. (Doodle, casting, etc.)
#define BUTTON 27
int buttonValue = 0;
int oldButtonValue = 0; // The wand knows if the button is pressed, what its prior state was,
int pressCount = 1; // and how many times it's been pressed. NOTE: Should start at 0, but I don't have the button rigged yet and want to check doodle mode. Doodle mode is "3".
int TotalNumberOfClicks = 0; // How many times has the mode trigger been triggered? (For debugging. Not essential for core functionality.)

// Variables for accelerometer data simplification
float xreal = 0;
float yreal = 0;
float zreal = 0;
float xprime = 0; // Smoothed and simplified
float yprime = 0; // Smoothed and simplified
float zprime = 0; // Smoothed and simplified
float primesum = xprime+yprime+zprime; // Sum of all three simplified accels

// The wand can be in one of 8 positions, associated with the numbers 0 to 7.
String positionQueue = "88888888888888888888"; // The wand remembers the last 20 actions.
// Note: Seeding the positionQueue string with more (or fewer) characters changes how many positions the wand remembers.
String currentPosition = "8"; // The wand knows what its current positon is.
String recentPosition = "8"; // The wand knows what its most recent prior position was.

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
//////////////////////////////////////////////////////
/////////////// Gremlin Game Variables ///////////////
//////////////////////////////////////////////////////

CRGB alienAttackerColor; // Stores alien attacker info.
CRGB energyBallColor; // Stores energy ball color.
CRGB nextEnergyBallPosition; // Stores energy ball color.
int attackTimer = 0;
int numberOfAliens = 0;
int numberOfTurnsElapsed = 0;
int numberOfWins = 0;
int aliensBanished = 0;

//////////////////////////////////////////////////////////
/////////////// END Gremlin Game Variables ///////////////
//////////////////////////////////////////////////////////

88 89 90 91 92 93 94 95 96
///////////////////////// Spell Variables
// Eventually you'll put an array of spells here.
// The spell array should contain the spell name, mana cost,
// utility (attack, defense, other), type (earth, metal, etc.),
// magnitude (damage, defense, heal)

///////////////////////// CommsLibraries
// Will go here when I know what to do.

97
////////////////////////// Comms Setup
98 99 100
// Will go here when I know what to do

// Wand and Status Variables
101
int doodleHue = 0; // Hue only used in Doodle mode.
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
// int listenDuration = 0; //How long to wait before casting a spell - This might be unused...
long currentThreshold = millis(); //Start tracking the time in milliseconds.
int thresholdGapValue = 100; //Delay to gain mana; Default: to 1/10th of a second.
long nextThreshold = currentThreshold+thresholdGapValue; //Next threshold is in 1/10th of a second.
int currentMana = 150; // Start the duel with 150 points of mana.
int manaIncrementValue = 1; //You gain 1 mana every threshold; Default: 10 mana per second.
int maximumMana = 250; // Maximum mana storage; default: 200. Cast well and cast often!
int wandDurability = 1000; //Your wand starts with 1,000 points of durability (HP).
bool amIHexed = false; //You do not start hexed.
String mostRecentHex = "8888888888888"; //What were we hit with most recently?
long whenIWasHexed = 0; //What time was it when I was most recently hexed?
int hexGapValue = 15000; //Time to react to a hex; Default: 15 seconds.
String currentSpell = "8888888888888"; // What spell are you casting right now?
String mostRecentSpell = "8888888888888"; //What spell did we cast most recently?
int repeatedSpellCount = 0; // How many times have you cast the same spell?
int repeatedSpellPenalty = 20; // You lose 20 mana for each time you repeated the spell.
long whenIHexedTheOpponent = 0; // When did you most recently hex your opponent?
bool isOpponentHexed = false; // Your opponent starts off un-hexed.
// End wand and status variables

122 123 124 125
// FastLED Elemental Jet Variables
CRGBPalette16 gPal;
#define COOLING  30 // COOLING: How much does the air cool as it rises? Less cooling = taller flames.  More cooling = shorter flames.
#define SPARKING 30 // SPARKING: What chance (out of 255) is there that a new spark will be lit? Higher chance = more roaring fire.  Lower chance = more flickery fire.
126
// END FastLED Elemental Jet variables
127

128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
// FastLED Functions, Section 1
/** show() for ESP32
 *  Call this function instead of FastLED.show(). It signals core 0 to issue a show, 
 *  then waits for a notification that it is done.
 */
void FastLEDshowESP32()
{
    if (userTaskHandle == 0) {
        // -- Store the handle of the current task, so that the show task can
        //    notify it when it's done
        userTaskHandle = xTaskGetCurrentTaskHandle();

        // -- Trigger the show task
        xTaskNotifyGive(FastLEDshowTaskHandle);

        // -- Wait to be notified that it's done
        const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 200 );
        ulTaskNotifyTake(pdTRUE, xMaxBlockTime);
        userTaskHandle = 0;
    }
}

/** show Task
 *  This function runs on core 0 and just waits for requests to call FastLED.show()
 */
void FastLEDshowTask(void *pvParameters)
{
    // -- Run forever...
    for(;;) {
        // -- Wait for the trigger
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

        // -- Do the show (synchronously)
        FastLED.show();

        // -- Notify the calling task
        xTaskNotifyGive(userTaskHandle);
    }
}
// END FastLED Functions, section 1

////////// START SETUP //////////
void setup() 
{
  delay(3000); // 3 second delay for NeoPixel safety - don't want to burn them out!
173
  randomSeed(analogRead(0));
174 175 176 177 178
  Serial.begin(115200); // Sometimes 9600

  Serial.println("Huzzah32 + PropMaker Wing magic wand starting up!");
  Serial.println();

179
  // Turn on power to the PropMaker FeatherWing (LED JST strip connection and speaker)
180 181 182 183
  pinMode(33, OUTPUT);
  digitalWrite(33, HIGH);
  Serial.print ("PropMaker powered up!");

184 185 186 187 188
///// Audio Setup //////
  ledcSetup(0,1E5,12);
  ledcAttachPin(26,0); // Send audio out via pin 26. This is un-changable.
///// Audio Setup End /////

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
// Set up wireless comms START
// Set up wireless comms END

///////////////////////// Button Setup
/*  pinMode(BUTTON, INPUT); */

///////////////////////// Accelerometer Setup
  if (! lis.begin(0x18)) {   // change this to 0x19 for alternative i2c address
    Serial.println("Couldnt start");
    while (1);
  }
  Serial.println("LIS3DH found!");
  
  lis.setRange(LIS3DH_RANGE_8_G);   // 2, 4, 8 or 16 G!
  
  Serial.print("Range = "); Serial.print(2 << lis.getRange());  
  Serial.println("G");

// Adjust this number for the sensitivity of the 'click' force
// this strongly depend on the range! for 16G, try 5-10
// for 8G, try 10-20. for 4G try 20-40. for 2G try 40-80
#define CLICKTHRESHHOLD 80

    // 0 = turn off click detection & interrupt
    // 1 = single click only interrupt output
    // 2 = double click only interrupt output, detect single click
    lis.setClick(1, CLICKTHRESHHOLD);
///////////////////////// End Accelerometer Setup

// LED Setup
  // tell FastLED about the LED strip configuration
  FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
  //FastLED.addLeds<LED_TYPE,DATA_PIN,CLK_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);

  // set master brightness control
  FastLED.setBrightness(BRIGHTNESS);

  int core = xPortGetCoreID();
  Serial.print("Main code running on core ");
  Serial.println(core);

  // -- Create the FastLED show task
  xTaskCreatePinnedToCore(FastLEDshowTask, "FastLEDshowTask", 2048, NULL, 2, &FastLEDshowTaskHandle, FASTLED_SHOW_CORE);
}
// END SETUP

// FastLED pattern variables START
// List of patterns to cycle through.  Each is defined as a separate function below.
//typedef void (*SimplePatternList[])();
//SimplePatternList gPatterns = { rainbow, rainbowWithGlitter, confetti, sinelon, juggle, bpm };
//uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current
uint8_t gHue = 0; // rotating "base color" used by many of the patterns

#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
// FastLED pattern variables END

/////////////////////////////////////////////////////////////////////////
////////////////////////// Main Wand Loop ///////////////////////////////
/////////////////////////////////////////////////////////////////////////

void loop()
{
  clickCheck(); // Check fora click. Update click counter, if needed. (Used to be a button checker.)

  // Change what the wand is doing based on the number of clicks.
  // Dream (quite) Mode
  if (pressCount==1) {
256
    ledZero();
257 258 259 260 261 262 263 264 265 266 267
    addGlitter (20); // What's wrong with a little sparkle power? NUTTIN'!
    FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
  }
  // Dazzle Mode
  if (pressCount==2) {
    dazzleMode ();
  }
  // Doodle Mode
  if (pressCount ==3) {
    doodleMode ();
  }
268
  // Doodle Mode 2
269
  if (pressCount ==4) {
270 271 272 273
    doodleMode2 ();
  }
  // Daylight (Flashlight) Mode
  if (pressCount ==5) {
274 275 276
    fill_solid(leds, NUM_LEDS, 0xFFFFFF); // Fill White.
    FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
  }
277 278 279 280
  // Gremlin AttackMode
  if (pressCount ==6) {
    gremlinGame ();
  }
281
  // Spell Casting Mode
282
  if (pressCount ==7) {
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
    duel ();
  }
}

////////////////////////////////////////////////////////////////////////
////////////////////////// End Wand Loop ///////////////////////////////
////////////////////////////////////////////////////////////////////////

////// Dazzle Mode
void dazzleMode () {
  juggle ();
  FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
  }
////// END Dazzle Mode

298
////// Doodle Mode // Tilt left and right to change the color flowing from the tip.
299
void doodleMode () {
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
  CRGB doodleBallColor;
  smoothData();
  translatePosition();
  if (currentPosition != recentPosition) { 
    // Did the position change? If so...
    recentPosition = currentPosition; // Update the old position to match the current position.
    // select a color based on the direction varaible and fill the handle with it
    Serial.print (currentPosition);Serial.print(" | ");
    if (currentPosition == "d") {
      doodleBallColor = (CRGB::Green);
    } else if (currentPosition == "D") {
      doodleBallColor = (CRGB::IndianRed);
    } else if (currentPosition == "A") {
      doodleBallColor = (CRGB::White);
    } else if (currentPosition == "U") {
      doodleBallColor = (CRGB::Cyan);
    } else if (currentPosition == "u") {
      doodleBallColor = (CRGB::Yellow);
    } else if (currentPosition == "B") {
      doodleBallColor = (CRGB::Magenta);
    } else if (currentPosition == "L") {
      doodleBallColor = (CRGB::Red);
    } else if (currentPosition == "R") {
      doodleBallColor = (CRGB::Blue);
    }
  }
  // Paint the wand!
  Serial.println (currentPosition);
  for (int ledCount = 0; ledCount <14; ledCount++) {
    leds[ledCount] = leds[ledCount+1]; // Copy the left side of the wand (from 1 to 14) to leds[0 to 13]
    leds[29-(ledCount)] = leds[29-(ledCount+1)]; // Copy the right side of the wand (from 15 to 28) to leds[16 to 29]
  }
  //write the current color to the tip (14 and 15)
  leds[14]=doodleBallColor;
  leds[15]=doodleBallColor;
335 336
  FastLEDshowESP32();
  FastLED.delay(1000/FRAMES_PER_SECOND); // insert a delay to keep the framerate modest
337 338 339
}
////// END Doodle Mode

340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
////// Doodle Mode2 - energyball!
void doodleMode2 () {
  CRGB doodleBallColor;
  smoothData();
  translatePosition();
  if (currentPosition != recentPosition) { 
    // Did the position change? If so...
    recentPosition = currentPosition; // Update the old position to match the current position.
    // select a color based on the direction varaible and fill the handle with it
    if (currentPosition == "d") {
      doodleBallColor = (CRGB::Green);
    } else if (currentPosition == "D") {
      doodleBallColor = (CRGB::IndianRed);
    } else if (currentPosition == "A") {
      doodleBallColor = (CRGB::White);
    } else if (currentPosition == "U") {
      doodleBallColor = (CRGB::Cyan);
    } else if (currentPosition == "u") {
      doodleBallColor = (CRGB::Yellow);
    } else if (currentPosition == "B") {
      doodleBallColor = (CRGB::Magenta);
    } else if (currentPosition == "L") {
      doodleBallColor = (CRGB::Red);
    } else if (currentPosition == "R") {
      doodleBallColor = (CRGB::Blue);
    } 
    // We have our color, let's make a shooting star!
    for (int ballStutter = 0; ballStutter < 2; ballStutter++) {
      for (int ledCount = 1; ledCount <15; ledCount++) {
        fill_gradient_RGB(leds, ledCount, doodleBallColor , ledCount-1, CRGB::Black);// Draw the ball on the left
        fill_gradient_RGB(leds, 29-ledCount, doodleBallColor , 30-ledCount, CRGB::Black); // Draw the ball on the right side
        FastLEDshowESP32();
        FastLED.delay(1000/FRAMES_PER_SECOND); // insert a delay to keep the framerate modest
      }
    }
  }
}
////// END Doodle Mode2

//////// Doodle Mode 3 ////////
void doodleMode3 () {
  //This routine "paints" the 8 pairs of LEDs all along the wand.
  int leftLedTip = (NUM_LEDS/2)-1; // 14 if NUM_LEDS is 30. Sets the tip for a range of 14 to 0 
  int rightLedTip = (NUM_LEDS/2); // 15 if NUM_LEDS is 30. Sets the tip for a range of 15 to 29
  smoothData();
  translatePosition();
  if (currentPosition != recentPosition) { 
    // Did the position change? If so...
    recentPosition = currentPosition; // Update the old position to match the current position.
    // Move all the LEDs down three steps from the tip.
    for (int shiftTheBits = 0; shiftTheBits < 12; shiftTheBits++) {
      leds[shiftTheBits] = leds[shiftTheBits+3]; // left side
      leds[29-shiftTheBits] = leds[26-shiftTheBits]; // right side
    }
//    for (int shiftTheBits = 0; shiftTheBits < 15; shiftTheBits++) {
//      leds[shiftTheBits] = leds[shiftTheBits+1]; // left side
//      leds[29-shiftTheBits] = leds[28-shiftTheBits]; // right side
//      FastLEDshowESP32();      
    }
    // Paint the tip
  if (currentPosition == "d") {
    // NOTE: All of the colors used below (and more) are part of hte FastLED library.
     // For all colors with samples, see: https://github.com/FastLED/FastLED/wiki/Pixel-reference
    leds[leftLedTip] = CRGB::Green; leds[rightLedTip] = CRGB::Green;
  } else if (currentPosition == "D") {
    leds[leftLedTip] = CRGB::IndianRed; leds[rightLedTip] = CRGB::IndianRed;
  } else if (currentPosition == "A") {
    leds[leftLedTip] = CRGB::White; leds[rightLedTip] = CRGB::White;
  } else if (currentPosition == "U") {
    leds[leftLedTip] = CRGB::Cyan; leds[rightLedTip] = CRGB::Cyan;
  } else if (currentPosition == "u") {
    leds[leftLedTip] = CRGB::Yellow; leds[rightLedTip] = CRGB::Yellow;
  } else if (currentPosition == "B") {
    leds[leftLedTip] = CRGB::Magenta; leds[rightLedTip] = CRGB::Magenta;
  } else if (currentPosition == "L") {
    leds[leftLedTip] = CRGB::Red; leds[rightLedTip] = CRGB::Red;
  } else if (currentPosition == "R") {
    leds[leftLedTip] = CRGB::Blue; leds[rightLedTip] = CRGB::Blue;
  }
  // Blend from almost the tip to three LEDs in..
  fill_gradient_RGB(leds, 14, leds[14], 11, leds[11]);
  fill_gradient_RGB(leds, 15, leds[15], 18, leds[11]);
  FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
}
//////// END Doodle Mode 3 ////////

///////////////////////////////////////////////////////////////////////
///////////////////////Main Gremlin Game Loop//////////////////////////
///////////////////////////////////////////////////////////////////////
void gremlinGame() { 
  getGremlinWandOrientation();
  updateAliens();
}
//////////////////////////////////////////////////////////////////////
///////////////////////END Gremlin Game Loop//////////////////////////
//////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////
///////////////////////Gremlin Game Procedures//////////////////////////
////////////////////////////////////////////////////////////////////////

void getGremlinWandOrientation() {
  smoothData ();
  translatePosition();
  if (currentPosition != recentPosition) // Did the position change? If so...
  {
    recentPosition = currentPosition; // Update the old position to match the current position.
    positionQueue += currentPosition; // Add the new position to the Position Queue.
    positionQueue.remove(0,1);  // Serial.println("New Position Queue:");  Serial.println(positionQueue); // Remove the oldest character from the left of the Position Queue string.
    setEnergyBallColor();
    checkForThrow();
  }
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////// Gun Update Stuff //////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

void setEnergyBallColor() {
  // select a color based on the direction varaible and fill the handle with it
//  if (currentPosition == "d") {
//    energyBallColor = (CRGB::Green);
//  } else if (currentPosition == "D") {
//    energyBallColor = (CRGB::IndianRed);
//  } else if (currentPosition == "A") {
//    energyBallColor = (CRGB::White);
//  } else if (currentPosition == "U") {
//    energyBallColor = (CRGB::Cyan);
//  } else if (currentPosition == "u") {
//    energyBallColor = (CRGB::Yellow);
//  } else if (currentPosition == "B") {
//    energyBallColor = (CRGB::Magenta);
/*  } else*/ if (currentPosition == "L") {
    energyBallColor = (CRGB::Red);
  } else if (currentPosition == "R") {
    energyBallColor = (CRGB::Blue);
  }
  displayEnergyBall();
}

void displayEnergyBall() {
  // We have our color, let's display it!
  leds[0] = energyBallColor;
  leds[NUM_LEDS-1] = energyBallColor;
  FastLED.show();
}

void checkForThrow() {
  // If the wand is pointing up or down, throw the ball.
  if ((currentPosition == "d") || (currentPosition == "D") || (currentPosition == "U") || (currentPosition == "u")) {
    throwTheBall();
  }
}

void throwTheBall () {
  int collision = 0;
  int energyBallPosition = 0;
  while (collision==0) {
    nextEnergyBallPosition = leds[energyBallPosition+1]; // Peek ahead one step in the strip.
    if (!nextEnergyBallPosition) { // If the next space is vacant... Note - ( leds[i] ) is false if vacant and true if it has any non-Black value.
      leds[energyBallPosition] = CRGB::Black; // Black out the current energy ball postion,
      leds[29-energyBallPosition] = CRGB::Black; // Black out the current energy ball postion,
      energyBallPosition ++; // move a step ahead
      leds[energyBallPosition] = energyBallColor; // Draw the e-ball in its new location
      leds[29-energyBallPosition] = energyBallColor; // Draw the e-ball in its new location
      FastLED.show(); // and update the display
      delay(20); // Slow the ball down just a little...
    } else {
      collision = 1; // the energy ball has hit an alien!
      alienHit();
    }
  }
}

void alienHit() {
  Serial.println("Alien hit!");
  CRGB alienColor = leds[15+numberOfAliens-1]; // look at the first alien in line
  int hitAlienRed = leds[15+numberOfAliens-1].red; // what is the red value of the first alien in line?
  int energyBallRed = energyBallColor.red; // what is the red value of the energy ball?
  int hitAlienBlue = leds[15+numberOfAliens-1].blue; // what is the blue value of the first alien in line?
  int energyBallBlue = energyBallColor.blue; // what is the blue value of the energy ball?
  if (energyBallColor == alienColor) { // If the alien and the energy ball match colors
    Serial.println("Alien destroyed!");
    leds[15+numberOfAliens-1] = CRGB::Black; // erase the alien on the left
    leds[15+numberOfAliens] = CRGB::Black; // erase the bullet on the left
    leds[14-numberOfAliens+1] = CRGB::Black; // erase the alien on the right
    leds[14-numberOfAliens] = CRGB::Black; // erase the bullet on the right
    numberOfAliens --; // reduce the # of aliens - do this after you erase them or your count is off!
    aliensBanished ++; // increase the number of aliens banished.
    FastLED.show();
    if (aliensBanished > 99) {superWinLedFx();loseTheGame();}
    if (numberOfAliens < 1){winTheGame();}
  } else {
    Serial.println("Wrong color!!");
    leds[15+numberOfAliens] = CRGB::Black; // erase the bullet on the left
    leds[14-numberOfAliens] = CRGB::Black; // erase the bullet on the right
    FastLED.show();
    // add one to the mistake counter
  }
}

void winTheGame() {
  Serial.println("You won!");
  numberOfWins ++;
  for (int j=0; j<numberOfWins; j++) {
    for (int i=0; i<256; i++) {
      fill_rainbow( leds, NUM_LEDS, i, 10);
      FastLED.show(); // update the strip with all changes
    }
  }
  FastLED.clear (); // Clear the strip
  FastLED.show(); // update the strip with all changes
  if (aliensBanished > 99) {
    superWinLedFx();
  }
  restartTheGame();
}

void restartTheGame() {
  Serial.println("Resetting game.");
  if (numberOfAliens > 13) {numberOfTurnsElapsed=0;} // Player lost. Reset the turn and win count.
  numberOfAliens = 0;
  for (int headstart=0; headstart<2+numberOfWins; headstart++) { // Set up the board with three aliens.
    addAnAlien(); delay(100);
  }
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////// Alien Update Stuff //////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

void updateAliens() {
  int timeDecay = numberOfTurnsElapsed*10;
  if (timeDecay > 900) {timeDecay=900;}
  attackTimer ++ ;
  if (attackTimer > 1000-(timeDecay)) { // The game speeds with each passing turn
    attackTimer=0; // Reset the timer
    numberOfTurnsElapsed ++; // Add one to the number of turns elapsed.
    addAnAlien();
  }
}

void addAnAlien() {
  numberOfAliens ++; // Increment the number of aliens, of course!
  if (numberOfAliens > 14) {loseTheGame();} // If there are too many aliens, the base has been overwhelmed. End the game.
  selectARandomAlien();
  displayTheAlien();
}

void selectARandomAlien() {
  int randomAlien = random(2);
//  Serial.print (randomAlien);
  if (randomAlien < 1) {
    alienAttackerColor = (CRGB::Red);
  } else {
    alienAttackerColor = (CRGB::Blue);
  }
}

void displayTheAlien() {
  // Move all the aliens up one step.
  for (int alienCongaLine=numberOfAliens; alienCongaLine > 0; alienCongaLine--) {
    leds[15+alienCongaLine] = leds[15+alienCongaLine-1]; // Update the left side.
    leds[14-alienCongaLine] = leds[14-alienCongaLine+1]; // Update the right side.
  }
  leds[NUM_LEDS/2] = alienAttackerColor; // Display the alien on the left...
  leds[(NUM_LEDS/2)-1] = alienAttackerColor; // ...and on the right.
  FastLED.show();
}

void loseTheGame() {
  Serial.println("You lost!");
  displayScore();
  endGameFx();
  restartTheGame();
}

void endGameFx() {
  for (int i=0; i<256; i++) {
    fill_solid( leds, NUM_LEDS, CHSV(i,255,255-i));
    FastLED.show(); // update the strip with all changes
    delay(15);
  }
  FastLED.clear (); // Clear the strip
  FastLED.show(); // update the strip with all changes
}

void displayScore() {
  for (int roundScore=0; roundScore<numberOfWins; roundScore++) {
    // display nubmer of wins
  }
  int scorePosition=0;
  FastLED.clear (); // Clear the strip
  for (int alienScore=0; alienScore<aliensBanished; alienScore++) {
    leds[scorePosition]=CHSV(scorePosition*10, 255, 255);
    FastLED.show(); 
    scorePosition++; if (scorePosition>29) {scorePosition=0; fill_solid(leds, NUM_LEDS, CHSV(100,100,100));FastLED.show(); }
    delay(25);
  }
  numberOfWins = 0; aliensBanished = 0; // Game's over. Wipe the scores.
}

void superWinLedFx() {
  Serial.println("SUPER WIN!");
  for (int q=0; q<999; q++) {
    if (random(10)<1) {
      int randomHue=random(256);
      int randomLED=random(30);
      leds[randomLED] = CHSV ((randomHue), 200, 255);
      FastLED.show();
      delay(15);
      blur1d(leds, NUM_LEDS, 60);
    }
  }
}

//////////////////////////////////////////////////////////////////////////////
/////////////////////// END Gremlin Game Procedures //////////////////////////
//////////////////////////////////////////////////////////////////////////////



662 663 664 665 666 667 668 669 670 671 672
/////////////////////////////////////////////////////////////////////////
////////////////////////// Main Duel Loop ///////////////////////////////
/////////////////////////////////////////////////////////////////////////

void duel() {
  checkForManaGain(); // Is it time to gain mana?
  listenForASpell(); //Listen for an incoming hex
  getWandOrientation(); // Get Wand Orientation (and possibly cast a spell).
  listenForASpell(); //Listen for an incoming hex
  checkForHexTimeout(); // If you're hexed, have you failed to respond to your hex in time?
  checkForOpponentHexTimeout(); // Is your opponent un-hexed?
673
  displayStats();
674 675 676 677 678 679
}

////////////////////////////////////////////////////////////////////////
////////////////////////// End Duel Loop ///////////////////////////////
////////////////////////////////////////////////////////////////////////

680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
////// Click Check routine

void clickCheck() {
  // Check for click.
  uint8_t click = lis.getClick();
  if (click == 0) return;
  if (! (click & 0x30)) return;
  Serial.print("Click detected (0x"); Serial.print(click, HEX); Serial.print("): ");
  if (click & 0x10) Serial.print(" single click");
  Serial.println();
  pressCount += 1; // Increase the counter by 1.
  if (pressCount > 7) {pressCount = 1;} // Don't exceed the nubmer of wand modes!
  Serial.print("Mode #: "); Serial.println(pressCount);
  TotalNumberOfClicks += 1;
  Serial.print("Total Number of Clicks: "); Serial.println(TotalNumberOfClicks);
  ledZero(); // Clear the leds before changing modes.
  delay(250);
  return;
}

////// END Click Check routine

////// Duel supporting procedures //////

704 705 706 707 708 709 710 711 712 713
//Check to see if it's time to gain mana.
void checkForManaGain() {
  if (millis() >= nextThreshold) {
    currentThreshold = millis();
    nextThreshold = currentThreshold + thresholdGapValue;
    if (currentMana >= maximumMana)
      {
        currentMana = maximumMana; // Can not exceed max.mana limit.
      } else {
        currentMana += manaIncrementValue; // If not full, gain mana.
714
        // Serial.print("."); // ...and print a dot.
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
      }
  }
}

// Listen for an incoming spell
void listenForASpell() {
  // This may be moot - We may set up a listening daemon.
}

// Get the real accel of the want and smooth / simplfy it.
void smoothData () {
  /* Create an event structure to read the accelerometer with */
  sensors_event_t event; 
  lis.getEvent(&event);
  
  /* Set "real" accel variables equal to readings, refactored to one gee */
  xreal = event.acceleration.x/9.8;
  yreal = event.acceleration.y/9.8;
  zreal = event.acceleration.z/9.8;

  /* "Round" real gee readings to nearest .5 (with a reasonable noise buffer and "stickyness")
  and stash the rounded gee in the corresponding "prime" variable. */
  if ((xreal >= -1) && (xreal <= -.85)) {
    xprime =-1;
  } else if ((xreal >= -.65) && (xreal <= -.35)) {
    xprime =-.5;
  } else if ((xreal >= -.15) && (xreal <= .15)) {
    xprime =0;
  } else if ((xreal >= .35) && (xreal <= .65)) {
    xprime =.5;
  } else if ((xreal >= .85) && (xreal <= 1)) {
    xprime =1;
  }
   
  if ((yreal >= -1) && (yreal <= -.85)) {
    yprime =-1;
  } else if ((yreal >= -.65) && (yreal <= -.35)) {
    yprime =-.5;
  } else if ((yreal >= -.15) && (yreal <= .15)) {
    yprime =0;
  } else if ((yreal >= .35) && (yreal <= .65)) {
    yprime =.5;
  } else if ((yreal >= .85) && (yreal <= 1)) {
    yprime =1;
  }
   
  if ((zreal >= -1) && (zreal <= -.85)) {
    zprime =-1;
  } else if ((zreal >= -.65) && (zreal <= -.35)) {
    zprime =-.5;
  } else if ((zreal >= -.15) && (zreal <= .15)) {
    zprime =0;
  } else if ((zreal >= .35) && (zreal <= .65)) {
    zprime =.5;
  } else if ((zreal >= .85) && (zreal <= 1)) {
    zprime =1;
  } 
}

// Turn smoothed accel data into a  simplified position character:
// U = Up, D = Down, L = Left, R = Right, A = straight Ahead, B = pointing back
// u = ahead and "u"p, at a 45º angle, d = ahead and  "d"own at a 45º angle
// These eight positions are all the wand really needs to know in order to cast a spell
// Having more positions than this makes it easier to mess a spell up.
void translatePosition () {
  primesum = xprime+yprime+zprime;

  if (primesum == 1 || primesum == -1) {
    if (xprime == 1) {
784
      currentPosition = "A"; // Ahead (Was Right)
785
    } else if (xprime == -1) {
786
      currentPosition = "B"; // Back (or inverted) (Was Left)
787 788 789 790 791
    } else if (yprime == 1) {
      currentPosition = "D"; // Down --- ok
    } else if (yprime == -1) {
      currentPosition = "U"; // Up --- ok
    } else if (zprime == 1) {
792
      currentPosition = "L"; // Left (Was Ahead)
793
    } else if (zprime == -1) {
794
      currentPosition = "R"; // Right (Was Back)
795
    } else if (yprime == .5) {
796
      if (xprime == .5) { //was zprime
797 798
        currentPosition = "d"; // Ahead down @ 45º up ... ok
      }
799 800 801 802 803
    } else if (yprime == -.5) {
      if (xprime == -.5) {
        currentPosition = "B"; // Back, technically at 45º, but we're clustering it into the "back" category ... ok
      }
    }
804 805 806
  }

  if (primesum == 0 ) {
807 808
    if (yprime == -.5) { 
        if (xprime == .5) { // Was zprime
809 810 811 812 813 814 815 816 817 818 819 820 821
          currentPosition = "u"; // Ahead, up @ 45º ... ok
        }
    }
  }
}// END translatePosition

//Record the wand's orientation
void getWandOrientation() {
  smoothData ();
  translatePosition();
  if (currentPosition != recentPosition) // Did the position change? If so...
  {
    recentPosition = currentPosition; // Update the old position to match the current position.
822
    playPositionTone(); 
823 824 825 826 827 828
    positionQueue += currentPosition; // Add the new position to the Position Queue.
    positionQueue.remove(0,1);  Serial.println("New Position Queue:");  Serial.println(positionQueue); // Remove the oldest character from the left of the Position Queue string.
    castASpell(); // Since the wand's moved, check if a spell was cast.
  }
}

829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
void playPositionTone() {
  if (currentPosition=="A") {
    ledcWriteNote(0,NOTE_C,octave); delay(200);
    ledcWriteTone(0,0); delay(50);
  } else if (currentPosition=="d") {
    ledcWriteNote(0,NOTE_D,octave); delay(200);
    ledcWriteTone(0,0); delay(50);
  } else if (currentPosition=="u") {
    ledcWriteNote(0,NOTE_E,octave); delay(200);
    ledcWriteTone(0,0); delay(50);
  } else if (currentPosition=="L") {
    ledcWriteNote(0,NOTE_F,octave); delay(200);
    ledcWriteTone(0,0); delay(50);
  } else if (currentPosition=="U") {
    ledcWriteNote(0,NOTE_G,octave); delay(200);
    ledcWriteTone(0,0); delay(50);
  } else if (currentPosition=="D") {
    ledcWriteNote(0,NOTE_A,octave); delay(200);
    ledcWriteTone(0,0); delay(50);
  } else if (currentPosition=="R") {
    ledcWriteNote(0,NOTE_B,octave); delay(200);
    ledcWriteTone(0,0); delay(50);
  } else if (currentPosition=="B") {
    ledcWriteNote(0,NOTE_C,octave+1); delay(200);
    ledcWriteTone(0,0); delay(50);
  }
}

857 858 859 860 861
//Check to see if you cast a spell
void castASpell() {
//  if (!isOpponentHexed) { // If the opponent is not hexed, see if you can cast a spell...
/*    if (positionQueue.endsWith ("202")) // Start a duel - point straight up, then down, then up.
      {spellDuel();}
862
    else */if (positionQueue.endsWith ("ALAuUBUu")) // Fireball - Was: "DdAuUBUu" point down, then over  right shoulder, then straight ahead
863
      {spellFireball(); checkForAHex();}
864 865 866
    else if ((positionQueue.endsWith ("ALALBRA")) || (positionQueue.endsWith ("ALARBLA"))) // Firejet
      {spellFirejet(); checkForAHex();}
    else if (positionQueue.endsWith ("ARAuUBUu")) // Waterball - Was: "ARUuA" Starting straight, point right (clockwise) and back, then up,then point ahead ** ALT 62324
867
      {spellWaterball(); checkForAHex();}
868 869 870
    else if ((positionQueue.endsWith ("ARALBRA")) || (positionQueue.endsWith ("ARARBLA"))) // Waterjet
      {spellWaterjet(); checkForAHex();}
    else if (positionQueue.endsWith ("AuAuUBUu")) // Airball - Was: "ALARAL" Starting straight, twist wand CCW 90º, then CW unitl 90º, then back to 90º left.
871
      {spellAirball(); checkForAHex();}
872 873 874
    else if ((positionQueue.endsWith ("AuALBRA")) || (positionQueue.endsWith ("AuARBLA"))) // Airjet
      {spellAirjet(); checkForAHex();}
    else if (positionQueue.endsWith ("AdAuUBUu")) // Earthball - Was: "DdALA" point at the ground, then ahead, then left, then point straight ahead again.
875
      {spellEarthball(); checkForAHex();}
876 877
    else if ((positionQueue.endsWith ("AdALBRA")) || (positionQueue.endsWith ("AdARBLA"))) // Earthjet
      {spellEarthjet(); checkForAHex();}
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894
    else if ((positionQueue.endsWith ("ARAuUL")) || (positionQueue.endsWith ("ARAuUR"))) // Wall of Water - Was: "uRBUu" Starting out and 45º up, point to the right, then up, then ahead.
      {spellWaterWall(); checkForAHex();}
    else if ((positionQueue.endsWith ("ALAuUL")) || (positionQueue.endsWith ("ALAuUR"))) // Wall of Fire - Was: "ARBUB" Starting straight, twist CW until inverted (180º), then bend elbow to point at sky, then extend (keeping top down). 
      {spellFireWall(); checkForAHex();}
    else if ((positionQueue.endsWith ("AuAuUL"))|| (positionQueue.endsWith ("AuAuUR"))) // Wall of Air - Was: "ALBRA" Circle point around counterclockwise, toward self.
      {spellAirWall(); checkForAHex();}
    else if ((positionQueue.endsWith ("AdAuUL")) || (positionQueue.endsWith ("AdAuUR"))) // Wall of Earth - Was: "AuLB" Rising left block (Point ahead, then lift wand while twisting palm out to point wand left.)
      {spellEarthWall(); checkForAHex();}
    else if ((positionQueue.endsWith ("ARUuA")) || (positionQueue.endsWith ("ALUuA"))) // Juggle demo - Starting straight, point right (clockwise) and back, then up,then point ahead ** ALT 62324
      {ledJuggle();}
    else if (positionQueue.endsWith ("ALARAL")) // Glitter Rainbow demo - Starting straight, twist wand CCW 90º, then CW unitl 90º, then back to 90º left.
      {ledGlitterbow();}
    else if (positionQueue.endsWith ("DdALA")) // Confetti demo – Point at the ground, then ahead, then left, then point straight ahead again.
      {ledConfetti();}
    else if ((positionQueue.endsWith ("ALBRA")) || (positionQueue.endsWith ("ARBLA"))) // BPM demo - Circle point around counterclockwise, toward self.
      {ledBpm();}
    else
895
      Serial.print("Fizzle");// {spellFizzle(); /* Serial.print("Post-cast Position Queue ="); Serial.println(positionQueue); */}
896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022
  } /*else {
    Serial.println("Opponent is already hexed!"); // Opponent is already hexed. Gotta wait!
    spellFizzle();
  }
}*/

void adjustMana() {
  currentMana-=150; // For now, all spells cost 150 mana.
  checkForOvercasting(); // Did you over-spend?
}

void checkForOvercasting() { // If you don't have enough mana, your wand will lose durability.
  if (currentMana <0) {
    Serial.print("You've overcast. Deficit: "); /// Let the player know they overcast.
    Serial.print(currentMana);
    Serial.println(" mana.");
    wandDurability += currentMana; // Reduce the wand's durability by the deficit...
    Serial.print("Current Durability = "); // Let the player know the wand's current durability.
    Serial.println(wandDurability);
    currentMana = 0; // ...and set current mana to zero.
    checkForCollapse(); // Has the wand lost all of its durability?
  } else {
    displayCurrentMana();
  }
}

void checkForCollapse() { // If your wand's durability is zero or less, its matrix has collapsed and you lose the duel.
  if (wandDurability <= 0) {
    Serial.println("Oh no!");
    endTheDuel ();
  }
}

void endTheDuel () {
  Serial.println("Your wand's matrix has collapsed!");
  // Let your opponent know they won.
  //char radiopacket[20] = "You win!"; // Set the message.
  // Serial.print("Sending "); Serial.print(radiopacket); Serial.println(" to opponent."); // Let us know we're sending.
  // rf69.send((uint8_t *)radiopacket, strlen(radiopacket)); // Send the message.
  // rf69.waitPacketSent(); // Wait until they receive it.
  //   ^^^^^^^^^^^^^^ NOTE - This should hang the wand if no-one is listening.
  wandDurability = 1000; // For this prototype, we're just resetting the game.
}

void checkForAHex() {
  if (amIHexed) { // If you're hexed and... 
    if (mostRecentSpell.startsWith("Wall")) { // ...you cast a defense in response to the Hex.
      calculateDefense(); // Figure out how effective the defense was.
      Serial.print("Hex defended against. Current Durability = ");
      Serial.println(wandDurability);
      amIHexed = false; // Drop the hex.
    } else { // ...you ignored the hex and cast a non-defense spell.
      wandDurability -= random(80,120); // Take 80 to 120 points of damage.
      Serial.print("Hex ignored and triggered. Ouch! Current Durability = ");
      Serial.println(wandDurability);
      amIHexed = false; // Drop the hex.
      checkForCollapse(); // Did you lose?
    }
  }
}

void calculateDefense() {
  char hexIndex = mostRecentHex.charAt(0);
  Serial.print("Current hex starts with ");
  Serial.println(hexIndex);
  if (
    (currentSpell.endsWith("Water") && hexIndex == 70) || // Fire
    (currentSpell.endsWith("Fire") && hexIndex == 87) || // Water
    (currentSpell.endsWith("Air") && hexIndex == 69) || // Earth
    (currentSpell.endsWith("Earth") && hexIndex == 65) // Air
  ) {
    Serial.println("Perfect Defense!"); // Caster defended with the exact opposed element.
  } else if (
    (currentSpell.endsWith("Water") && hexIndex == 87) || // Water
    (currentSpell.endsWith("Fire") && hexIndex == 70) || // Fire
    (currentSpell.endsWith("Air") && hexIndex == 65) || // Air
    (currentSpell.endsWith("Earth") && hexIndex == 69) // Earth
  ) {
    Serial.println("Poor defense..."); // Caster used the same element as the attack.
    wandDurability -= random(80,120); // Take 40 to 60 points of damage.
  } else {
    Serial.println("Imperfect defense."); // Caster used an element orthogonal to the attack.
    wandDurability -= random(40,80); // Take 40 to 60 points of damage.
  } 
}

void checkForHexTimeout() {
  if (amIHexed) {
    if (millis() >= whenIWasHexed+hexGapValue ) { // If you've run out of time...
      wandDurability -= random(80,120); // ...take 80 to 120 points of damage.
      Serial.print("Hex timed out and triggered. Ouch! Current Durability = ");
      Serial.println(wandDurability);
      amIHexed = false; // Drop the hex.
      checkForCollapse();
    }
  }
}

void checkForOpponentHexTimeout() {
  long timeSinceHexingOpponent = millis()-whenIHexedTheOpponent;
  if ( timeSinceHexingOpponent >= hexGapValue) { // Did you cast your last attack more than 15 seconds ago?
    isOpponentHexed = false; // If you did, drop the flag. They can't be hexed for more than 15 seconds.
  //  Serial.println("Opponent un-hexed.");
  }
}

void displayCurrentMana() {
  Serial.print("Current mana: ");
  Serial.println(currentMana);
}

void checkForRepeatedSpell() {
  if (currentSpell==mostRecentSpell) {
    repeatedSpellCount +=1;
    currentMana -= repeatedSpellCount*repeatedSpellPenalty;
    Serial.print("Repeated spell penalty triggered. You've cast the same spell ");
    Serial.print(repeatedSpellCount+1);
    Serial.print(" times in a row. Additional mana cost: ");
    Serial.println(repeatedSpellCount*repeatedSpellPenalty);
    Serial.print("Current mana: ");
    Serial.println(currentMana);
    checkForOvercasting();
  } else {
    repeatedSpellCount = 0;
  }
}

1023
void displayStats() { // Show Mana and Durability and curent spell queue
1024
  float manaPercentage = (currentMana*1.0)/maximumMana;
1025
  int manaFillEnd = (4*manaPercentage)+1;
1026
  ledZero();
1027
  fill_gradient_RGB (leds,0,0x0000ff,manaFillEnd,0x000000); // Display current mana as a blue (0x0000ff) bar on the wand that goes from the third LED near the handle to whereever the "fill end" is. The fill end is black (0x000000).
1028
  float durabilityPercentage = wandDurability/1000.0;
1029 1030 1031
  int durabilityFillEnd = NUM_LEDS-2-(4*durabilityPercentage);
  fill_gradient_RGB (leds,NUM_LEDS-1,0xff0000, durabilityFillEnd,0x000000); // Display current mana as a red (oxff0000) bar on the wand that goes from the LED closest to the handle to whereever the "fill end" is. The fill end is black (0x000000).
  displayCurrentGestureString ();
1032 1033 1034
  FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
}

1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
void displayCurrentGestureString () {
  //This routine "paints" the 8 pairs of LEDs at the tip with the last 8 positions.
  //We're painting the whole thing instead of shifting LED vaules down and only painting the tip because spells perform full-wand animations, which would erase the LEDs we're referring to.
  String queueCharacter = "x";
  int leftLedTip = (NUM_LEDS/2)-1; // 14 if NUM_LEDS is 30. Sets the tip for a range of 14 to 0 
  int rightLedTip = (NUM_LEDS/2); // 15 if NUM_LEDS is 30. Sets the tip for a range of 15 to 29
  // start at the end (length) of the positionQueue (the 20th character) • end when you've read 8 characters • Reduce your read position by one per loop
  for (int charReadPointer=positionQueue.length(); charReadPointer > positionQueue.length()-9; charReadPointer--) {
    queueCharacter = positionQueue.charAt(charReadPointer); // What's the character at the ReadPointer?
    if (queueCharacter == "d") {
      // NOTE: All of the colors used below (and more) are part of hte FastLED library.
       // For all colors with samples, see: https://github.com/FastLED/FastLED/wiki/Pixel-reference
      leds[leftLedTip-(19-charReadPointer)] = CRGB::Green; leds[rightLedTip-(charReadPointer-19)] = CRGB::Green;
    } else if (queueCharacter == "D") {
      leds[leftLedTip-(19-charReadPointer)] = CRGB::IndianRed; leds[rightLedTip-(charReadPointer-19)] = CRGB::IndianRed;
    } else if (queueCharacter == "A") {
      leds[leftLedTip-(19-charReadPointer)] = CRGB::White; leds[rightLedTip-(charReadPointer-19)] = CRGB::White;
    } else if (queueCharacter == "U") {
      leds[leftLedTip-(19-charReadPointer)] = CRGB::Cyan; leds[rightLedTip-(charReadPointer-19)] = CRGB::Cyan;
    } else if (queueCharacter == "u") {
      leds[leftLedTip-(19-charReadPointer)] = CRGB::Yellow; leds[rightLedTip-(charReadPointer-19)] = CRGB::Yellow;
    } else if (queueCharacter == "B") {
      leds[leftLedTip-(19-charReadPointer)] = CRGB::Magenta; leds[rightLedTip-(charReadPointer-19)] = CRGB::Magenta;
    } else if (queueCharacter == "L") {
      leds[leftLedTip-(19-charReadPointer)] = CRGB::Red; leds[rightLedTip-(charReadPointer-19)] = CRGB::Red;
    } else if (queueCharacter == "R") {
      leds[leftLedTip-(19-charReadPointer)] = CRGB::Blue; leds[rightLedTip-(charReadPointer-19)] = CRGB::Blue;
    }
  }
//  FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip // Commented out because Show is called immediately after this in displayStats()
}

1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085
////////////////////////////// Misc LED FX

//////////////////////////////////////
/////// Fast LED FX procedures ///////
//////////////////////////////////////

void rainbow() 
{
  // FastLED's built-in rainbow generator
  fill_rainbow( leds, NUM_LEDS, gHue, 7);
}

void addGlitter( fract8 chanceOfGlitter) 
{
  if( random8() < chanceOfGlitter) {
    leds[ random16(NUM_LEDS) ] += CRGB::White;
  }
}

1086 1087 1088 1089 1090 1091 1092
void rainbowWithGlitter() 
{
  // built-in FastLED rainbow, plus some random sparkly glitter
  rainbow();
  addGlitter(80);
}

1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137
void confetti() 
{
  // random colored speckles that blink in and fade smoothly
  fadeToBlackBy( leds, NUM_LEDS, 10);
  int pos = random16(NUM_LEDS);
  leds[pos] += CHSV( gHue + random8(64), 200, 255);
}

void sinelon()
{
  // a colored dot sweeping back and forth, with fading trails
  fadeToBlackBy( leds, NUM_LEDS, 20);
  int pos = beatsin16( 13, 0, NUM_LEDS-1 );
  leds[pos] += CHSV( gHue, 255, 192);
}

void sineball(int ballHue)
{
  // a ballHue-colored dot sweeping back and forth, with fading trails
  fadeToBlackBy( leds, NUM_LEDS, 20);
  int pos = beatsin16( 13, 0, NUM_LEDS-1 );
  leds[pos] += CHSV( ballHue, 255, 192);
}

void bpm()
{
  // colored stripes pulsing at a defined Beats-Per-Minute (BPM)
  uint8_t BeatsPerMinute = 62;
  CRGBPalette16 palette = PartyColors_p;
  uint8_t beat = beatsin8( BeatsPerMinute, 64, 255);
  for( int i = 0; i < NUM_LEDS; i++) { //9948
    leds[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*10));
  }
}

void juggle() {
  // eight colored dots, weaving in and out of sync with each other
  fadeToBlackBy( leds, NUM_LEDS, 20);
  byte dothue = 0;
  for( int i = 0; i < 8; i++) {
    leds[beatsin16( i+7, 0, NUM_LEDS-1 )] |= CHSV(dothue, 200, 255);
    dothue += 32;
  }
}

1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177
void ElementalJetWithPalette(int jetColor) {
// Array of temperature readings at each simulation cell
  static byte heat[NUM_LEDS];

  // Step 0. Set the palette up
     uint8_t hue = jetColor; // Set the base hue to the jetcolor
     CRGB darkcolor  = CHSV(hue,255,192); // pure hue, three-quarters brightness
     CRGB lightcolor = CHSV(hue,128,255); // half 'whitened', full brightness
     gPal = CRGBPalette16( CRGB::Black, darkcolor, lightcolor, CRGB::White);
  
  // Step 1.  Cool down every cell a little
    for( int i = 0; i < NUM_LEDS; i++) {
      heat[i] = qsub8( heat[i],  random8(0, ((COOLING * 10) / NUM_LEDS) + 2));
    }
  
    // Step 2.  Heat from each cell drifts 'up' and diffuses a little
    for( int k= NUM_LEDS - 1; k >= 2; k--) {
      heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
    }
    
    // Step 3.  Randomly ignite new 'sparks' of heat near the bottom
    if( random8() < SPARKING ) {
      int y = random8(7);
      heat[y] = qadd8( heat[y], random8(160,255) );
    }

    // Step 4.  Map from heat cells to LED colors
    for( int j = 0; j < NUM_LEDS/2; j++) {
      // Scale the heat value from 0-255 down to 0-240
      // for best results with color palettes.
      byte colorindex = scale8( heat[j], 240);
      CRGB color = ColorFromPalette( gPal, colorindex);
      int pixelnumber;
      pixelnumber = j;
      leds[pixelnumber] = color;
      leds[29-pixelnumber] = color;
    }
}


1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297
/////////////////////////////////////////////////
/////////// END Fast LED FX procedures //////////
/////////////////////////////////////////////////

////////////////////////////// SPELL BOOK

/////////////////////////////
/////// Attack Spells ///////
/////////////////////////////

void spellFireball() // Casts a fireball
// Fireball is a standard attack spell.
// Water defenses perfectly counter it.
{
  adjustMana();
  currentSpell = "Fireball";
  checkForRepeatedSpell();
  mostRecentSpell = "Fireball";
  Serial.println("Fireball!");
// ?Transmit spell?
  isOpponentHexed = true;
  whenIHexedTheOpponent = millis();
  ledFireball();
  ledZero();
}

void ledFireball() {
  int fx_timer = 0;
  do {
    sineball(0); // Call the sineball function with red (0), updating the 'leds' array
    FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
    FastLED.delay(1000/FRAMES_PER_SECOND);   // insert a delay to keep the framerate modest
    fx_timer += 1;
  } while (fx_timer < 300);
  fx_timer = 0;
}

void spellEarthball() // Casts an earthball
// Earthball is a standard attack spell.
// Air defenses perfectly counter it.
{
  adjustMana();
  currentSpell = "Earthball";
  checkForRepeatedSpell();
  mostRecentSpell = "Earthball";
  Serial.println("Earthball!");
// ?Transmit spell?
  isOpponentHexed = true;
  whenIHexedTheOpponent = millis();
  ledEarthball();
  ledZero();
}

void ledEarthball() {
  int fx_timer = 0;
  do {
    sineball(96); // Call the sineball function with green (96), updating the 'leds' array
    FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
    FastLED.delay(1000/FRAMES_PER_SECOND);   // insert a delay to keep the framerate modest
    fx_timer += 1;
  } while (fx_timer < 300);
  fx_timer = 0;
}


void spellAirball() // Casts an airball
// Airball is a standard attack spell.
// Earth defenses perfectly counter it.
{
  adjustMana();
  currentSpell = "Airball";
  checkForRepeatedSpell();
  mostRecentSpell = "Airball";
  Serial.println("Airball!");
// ?Transmit spell?
  isOpponentHexed = true;
  whenIHexedTheOpponent = millis();
  ledAirball();
  ledZero();
}

void ledAirball() {
  int fx_timer = 0;
  do {
    sineball(60); // Call the sineball function with yellow (64), updating the 'leds' array
    FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
    FastLED.delay(1000/FRAMES_PER_SECOND);   // insert a delay to keep the framerate modest
    fx_timer += 1;
  } while (fx_timer < 300);
  fx_timer = 0;
}

void spellWaterball() // Casts a waterball
// Waterball is a standard attack spell.
// Fire defenses perfectly counter it.
{
  adjustMana();
  currentSpell = "Waterball";
  checkForRepeatedSpell();
  mostRecentSpell = "Waterball";
  Serial.println("Waterball!");
// ?Transmit spell?
  isOpponentHexed = true;
  whenIHexedTheOpponent = millis();
  ledWaterball();
  ledZero();
}

void ledWaterball() {
  int fx_timer = 0;
  do {
    sineball(144); // Call the sineball function with blue (160), updating the 'leds' array
    FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
    FastLED.delay(1000/FRAMES_PER_SECOND);   // insert a delay to keep the framerate modest
    fx_timer += 1;
  } while (fx_timer < 300);
  fx_timer = 0;

}

1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401
void spellFirejet() // Casts a jet of fire
// Firejet is a quick attack spell - it hits instantly
// Water defenses perfectly counter it.
{
  adjustMana();
  currentSpell = "Firejet";
  checkForRepeatedSpell();
  mostRecentSpell = "Firejet";
  Serial.println("Firejet!");
// ?Transmit spell?
  isOpponentHexed = false;
  ledFirejet();
  ledZero();
}

void ledFirejet() {
  int fx_timer = 0;
  do {
    ElementalJetWithPalette(0); // Call the Jet function with the red palette set
    FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
    FastLED.delay(1000/FRAMES_PER_SECOND);   // insert a delay to keep the framerate modest
    fx_timer += 1;
  } while (fx_timer < 300);
  fx_timer = 0;
}

void spellWaterjet() // Casts a jet of water
// Waterjet is a quick attack spell - it hits instantly
// Fire defenses perfectly counter it.
{
  adjustMana();
  currentSpell = "Waterjet";
  checkForRepeatedSpell();
  mostRecentSpell = "Waterjet";
  Serial.println("Waterjet!");
// ?Transmit spell?
  isOpponentHexed = false;
  ledWaterjet();
  ledZero();
}

void ledWaterjet() {
  int fx_timer = 0;
  do {
    ElementalJetWithPalette(144); // Call the Jet function with the blue palette set
    FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
    FastLED.delay(1000/FRAMES_PER_SECOND);   // insert a delay to keep the framerate modest
     fx_timer += 1;
  } while (fx_timer < 300);
  fx_timer = 0;
}

void spellEarthjet() // Casts a jet of earth
// Earthjet is a quick attack spell - it hits instantly
// Air defenses perfectly counter it.
{
  adjustMana();
  currentSpell = "Earthjet";
  checkForRepeatedSpell();
  mostRecentSpell = "Earthjet";
  Serial.println("Earthjet!");
// ?Transmit spell?
  isOpponentHexed = false;
  ledEarthjet();
  ledZero();
}

void ledEarthjet() {
  int fx_timer = 0;
  do {
    ElementalJetWithPalette(96); // Call the Jet function with the green palette set
    FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
    FastLED.delay(1000/FRAMES_PER_SECOND);   // insert a delay to keep the framerate modest
    fx_timer += 1;
  } while (fx_timer < 300);
  fx_timer = 0;
}

void spellAirjet() // Casts a jet of air
// Airjet is a quick attack spell - it hits instantly
// Earth defenses perfectly counter it.
{
  adjustMana();
  currentSpell = "Airjet";
  checkForRepeatedSpell();
  mostRecentSpell = "Airjet";
  Serial.println("Airjet!");
// ?Transmit spell?
  isOpponentHexed = false;
  ledAirjet();
  ledZero();
}

void ledAirjet() {
  int fx_timer = 0;
  do {
    ElementalJetWithPalette(60); // Call the Jet function with the green palette set
    FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
    FastLED.delay(1000/FRAMES_PER_SECOND);   // insert a delay to keep the framerate modest
    fx_timer += 1;
  } while (fx_timer < 300);
  fx_timer = 0;
}

1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421
//////////////////////////////
/////// Defense Spells ///////
//////////////////////////////

void spellFireWall() // Creates a wall of fire
// Wall of Fire is a standard defense spell.
// It perfectly counters water attacks.
{
  adjustMana(); 
  currentSpell = "Wall of Fire";
  checkForRepeatedSpell();
  mostRecentSpell = "Wall of Fire";
  Serial.println("Wall of Fire!");
  ledFirewall();
  ledZero();
}

void ledFirewall() {
  fill_solid(leds, NUM_LEDS, CRGB::Red);
  FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
1422 1423
  delay(1500);
  ledZero();
1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441
}

void spellAirWall() // Creates a wall of air
// Wall of Air is a standard defense spell.
// It perfectly counters Earth attacks.
{
  adjustMana(); 
  currentSpell = "Wall of Air";
  checkForRepeatedSpell();
  mostRecentSpell = "Wall of Air";
  Serial.println("Wall of Air!!");
  ledAirwall();
  ledZero();
}

void ledAirwall() {
  fill_solid(leds, NUM_LEDS, CRGB::Yellow);
  FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
1442 1443
  delay(1500);
  ledZero();
1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463
}



void spellEarthWall() // Creates a wall of earth
// Wall of Earth is a standard defense spell.
// It perfectly counters Air attacks.
{
  adjustMana(); 
  currentSpell = "Wall of Earth";
  checkForRepeatedSpell();
  mostRecentSpell = "Wall of Earth";
  Serial.println("Wall of Earth!");
  ledEarthwall();
  ledZero();
}

void ledEarthwall() {
  fill_solid(leds, NUM_LEDS, CRGB::Green);
  FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
1464 1465
  delay(1500);
  ledZero();
1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481
}

void spellWaterWall() // Creates a wall of water
{
  adjustMana(); 
  currentSpell = "Wall of Water";
  checkForRepeatedSpell();
  mostRecentSpell = "Wall of Water";
  Serial.println("Wall of Water!");
  ledWaterwall();
  ledZero();
}

void ledWaterwall() {
  fill_solid(leds, NUM_LEDS, CRGB::Blue);
  FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
1482 1483
  delay(1500);
  ledZero();
1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515
}

///////////////////////////
/////// Misc Spells ///////
///////////////////////////

/*
void spellDuel() // Starts a duel
{
  // Dueling does not cost mana.
  mostRecentSpell = "Duel";
  Serial.println("Duel!");
  ledDuel();
  ledZero();
}

void ledDuel()
{
  for (int x=0; x <= 2; x++)
  {
    for (int i=255; i >= 105; i--)
    {
      // analogWrite(RED, i);
      analogWrite(GREEN, i);
      analogWrite(BLUE, i);
      delay(5);
      ledZero();
    }
  }
}
*/

1516
void fastleddemoshow() {
1517 1518 1519 1520
    FastLEDshowESP32(); // send the 'leds' array out to the actual LED strip
    FastLED.delay(1000/FRAMES_PER_SECOND);   // insert a delay to keep the framerate modest
    // do some periodic updates
    EVERY_N_MILLISECONDS( 20 ) { gHue++; } // slowly cycle the "base color" through the rainbow
1521 1522 1523 1524 1525 1526 1527
}

void ledJuggle() {
  int fx_timer = 0;
  do {
    juggle(); // Call the juggle function, updating the 'leds' array
    fastleddemoshow();
1528
    fx_timer += 1;
1529
  } while (fx_timer < 500);
1530 1531 1532 1533 1534 1535
  fx_timer = 0;
}

void ledBpm() {
  int fx_timer = 0;
  do {
1536 1537
    bpm(); // Call the bpm function, updating the 'leds' array
    fastleddemoshow();
1538
    fx_timer += 1;
1539
  } while (fx_timer < 500);
1540 1541 1542 1543 1544 1545
  fx_timer = 0;
}

void ledGlitterbow() {
  int fx_timer = 0;
  do {
1546 1547
    rainbowWithGlitter(); // Call the glitterbow function, updating the 'leds' array
    fastleddemoshow();
1548
    fx_timer += 1;
1549
  } while (fx_timer < 500);
1550 1551 1552 1553 1554 1555
  fx_timer = 0;
}

void ledConfetti() {
  int fx_timer = 0;
  do {
1556 1557
    confetti(); // Call the confetti function, updating the 'leds' array
    fastleddemoshow();
1558
    fx_timer += 1;
1559
  } while (fx_timer < 500);
1560 1561 1562 1563 1564 1565
  fx_timer = 0;
}

void spellFizzle() // Lets player know wand changed position.
{
  // Fizzling does not cost mana.
1566
  // mostRecentSpell = "Fizzle"; // Technically, fizzling is not a spell. Should the wand retain the most recent "real" spell?
1567 1568 1569 1570 1571 1572 1573
  Serial.println("***fizzle!***");
  ledFizzle();
  ledZero();
}

void ledFizzle() {
  // select a color based on the direction varaible and fill the strip with it
1574
  if (currentPosition == "d") {
1575
    fill_solid(leds, NUM_LEDS, CRGB::Green);
1576
  } else if (currentPosition == "D") {
1577 1578 1579 1580
    fill_solid(leds, NUM_LEDS, 0xDDA0DD);
  } else if (currentPosition == "A") {
    fill_solid(leds, NUM_LEDS, 0xFFFFFF); // White
  } else if (currentPosition == "U") {
1581 1582
    fill_solid(leds, NUM_LEDS, 0x00FFFF); // Cyan
  } else if (currentPosition == "u") {
1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597
    fill_solid(leds, NUM_LEDS, CRGB::Yellow);
  } else if (currentPosition == "B") {
    fill_solid(leds, NUM_LEDS, 0xFF00FF); // Magenta
  } else if (currentPosition == "L") {
    fill_solid(leds, NUM_LEDS, CRGB::Red);
  } else if (currentPosition == "R") {
    fill_solid(leds, NUM_LEDS, 0x0000FF); // Blue
  } 
  FastLEDshowESP32(); // display it
  int fx_timer = 0;
  while (fx_timer < 300) {fx_timer+=1;} //wait a moment
  fx_timer = 0; // reset the timer
  ledZero(); // ...and remove the colors
}

1598
/// Clear LEDS 
1599 1600 1601
void ledZero() {
  FastLED.clear ();
  FastLEDshowESP32(); // update the strip with all changes
1602
}