回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

【Arduino 动手做】RGB-Brick:100颗WS2812 LED 构建了一个 10x10 LED 魔方 简单

头像 驴友花雕 2025.07.12 4 0

不久前,我用其中一些 WS2812 LED 构建了一个 10x10 LED-Coffetable,但即使可以在连接智能手机的情况下玩老式游戏 Snake,我也想要更特别的东西。所以我决定再给它放几个 LED,排列成一个立方体,以便在创建动画和游戏时获得更多可能性,这就是我们:RGB-Brick。

这里有一些图片和一个小视频,其中包含 Brick 的一些功能,包括大量动画、用于加热气氛的(正在开发中的)火焰、音乐可视化器以及游戏 Snake 和 Tetris。

以下是您需要的所有材料的清单,其中一些不是必需的,其他一些可以根据您的喜好进行交换:
500 个 WS2812 LED 30 像素/米
5V 30A 电源
小小 3.2
ESP8266 wifi-modul
一些木头:
1x:27.2 厘米 x 27.2 厘米 x 1.0 厘米,用于盖子
2x:29.6 厘米 x 27.2 厘米 x 1.0 厘米,用于大侧板
2x:25.2 厘米 x 29.6 厘米 x 1.0 厘米,用于小侧板
1x:34.0 厘米 x 34.0 厘米 x 1.9 厘米,用于底部
8x:34.0cm x 4.6cm x 0.3cm,用于 LED 网格的边缘
100x:34.0 厘米 x 3.3 厘米 x 0.3 厘米,用于 LED 网格
一些亚克力玻璃片:
1x:34.0 厘米 x 34.0 厘米 x 0.3 厘米
2x:34.0 厘米 x 36.3 厘米 x 0.3 厘米
2x:34.6 厘米 x 36.3 厘米 x 0.3 厘米
1x:10.0cm x 7.5cm x 0.3cm(可选,用于终端)
Teensy 音频板(可选)
电线、稳压器、电缆夹、蜂鸣器、按钮、温度传感器(可选)
木头胶、亚克力玻璃胶、螺丝等小物
如果你想在立方体的底部有一个端子(除了电源插孔之外,它是可选的):
230V 电源插孔
230V 开关
音频插孔
USB 延长线

首先,我们将构建木箱和 LED 网格。立方体的尺寸由 LED 灯条上像素的距离指定。在这种情况下,像素的距离为 3,4 厘米,因此立方体必须为 34 x 34 x 34 厘米。使用此尺寸将节省大量时间,因为您无需在每个像素后切割条带并通过小电缆将其重新组装在一起。

所有产品都配有一些木胶。你必须正常工作,因为亚克力外壳与木箱的顶部完美匹配。有你身边的一些志愿者会变得容易得多,或者像我一样使用框架张紧器。

网格的边缘和网格本身由高密度纤维板 (HDF) 制成。使用台锯是最好的选择,因为您必须切割 100 多件。您可以在上图中找到尺寸。网格需要每 0,3 厘米有一个小间隙(约 3,4 厘米),以便将 x 和 y 壁架放在一起。完成后,您可以将边缘放在立方体上并用大量木胶固定它们。这有点困难,特别是因为它们的角度应该接近 45 度左右。在将网格连接到立方体之前,您必须添加 LED 灯条。

侧面的 LED 灯条绕立方体转一圈,因此切割 10 个长度为 40 像素的灯条。对于立方体顶部的 LED,切割 10 个长度为 10 像素的条带。小心地根据条带上的箭头正确对齐条带。一旦你从立方体上取下胶条,它就不会像第一次那样保持住。
电源用一些螺丝固定在内部的侧面。LED 的电源线通过每个 LED 灯条附近的一些小孔进入盒子。
控制器由一个 Teensy 3.2、一个 ESP8266 和 Teensy 音频板组成,运行立方体不需要。DHT11 仅用于检查立方体内部的温度,但经过大约几个小时的多次测试,我可以说您可以将其省略。
在终端上,您可以找到电源插孔和电源开关(当我意识到这不是切换的最佳位置时,为时已晚)。USB 插孔用于对 Teensy 进行编程。音频输入转到 Teensy 音频板,以便根据音乐执行 LED。所有这一切都汇集在一个由两个铝型材固定的小型芳基玻璃上。刚在车库里找到这个,你可以使用任何你想要的东西,因为它被底部木板覆盖,对立方体的外观没有贡献。
请注意,一个 LED 使用 60mA,总共是 30A!连接它们时要小心!在将电路连接到电源之前,您必须验证所有电路!

完成之前的所有步骤后,就可以将所有部分组合在一起了。当您还没有合并 LED 网格时,现在是时候这样做了。我不会将网格粘在立方体上,因为不需要它,如果 LED 损坏,您可以毫无问题地更换它,但是您需要两只以上的手才能将五个网格固定到立方体上并将其放入亚克力盖中。最后但并非最不重要的一点是,您可以将底部的木板拧到立方体上。盖子通过八个非常小的螺丝固定在底部木板上。

Teensy 上的草图基于 FastLED 库,其中包括几个基本动画。将 RGBLEDS 库包添加到您的草图中,可以带来强大的矩阵代数,用于显示文本和“精灵”以及大量示例草图。如果您还想玩俄罗斯方块,请参考 jollifactory 的 instructable,即使它只使用双色矩阵。
智能手机应用程序基于 David Eickhoff 的 NetIO,它有一个非常好的文档。使用 NetIO-UI-Designer,您可以创建自己的用户界面,其中包含按钮、滑块、标签等。您可以在设计器中选择传出消息的协议。就我而言,我选择了最简单的一个 - UDP。这些消息通过我的家庭网络发送到 ESP8266,Teensy 将评估内容并处理指定的命令。您可以使用附件开始创建自己的界面,或者只使用您选择的应用程序。
 

00.jpg
01.jpg
02.jpg
03.jpg
04.jpg
05.jpg
06.jpg
07.jpg
08.jpg
09.jpg
10.jpg

项目代码

 

 

代码
#define FASTLED_ALLOW_INTERRUPTS 0

#include <FastLED.h>
#include <LEDMatrix.h>
#include <LEDText.h>
#include <FontMatrise.h>
#include <LEDSprites.h>
#include <EEPROM.h>
#include <Wire.h>
#include <SD.h>
#include <SPI.h>
#include <Audio.h>
//dht -> 16 (A2)



// GUItool: begin automatically generated code
AudioPlaySdWav           wavEffects;
AudioInputI2S            audioInput;
AudioOutputI2S           audioOutput;
AudioMixer4              mixerInput;
AudioAnalyzeFFT1024      fft;
AudioAnalyzePeak         peak1;

AudioConnection          patchCord5(wavEffects, 0, audioOutput, 1);
AudioConnection          patchCord6(wavEffects, 1, audioOutput, 0);

AudioConnection          patchCord7(audioInput, 0, mixerInput, 0);
AudioConnection          patchCord8(audioInput, 1, mixerInput, 1);
AudioConnection          patchCord9(mixerInput, fft);
AudioConnection          patchCord10(mixerInput, peak1);

AudioControlSGTL5000     audioShield;

const int nSoundeffects = 10;
const char * soundeffects[10] = { "COIN.WAV", "COIN2.WAV", "BOSSFIGHT.WAV", "BONUS.WAV", "JUMP.WAV",
                                  "PITCH.WAV",  "GAMEOVER.WAV", "HUNGERGAMES.WAV", "LEVELUP.WAV", "SUCCESS.WAV"
                                };
boolean playeffects = false;

float fft_values[40];
int fft_show[40];
float peak;
int peak_show;
byte xx1, yy1, bb1;
float vol = 0.5;

uint16_t gradient = 0;
uint8_t volume = 0, last = 0;
float maxVol = 15;
float avgVol = 0;
float avgBump = 0;
bool bump = false;
#define LED_TOTAL 15
#define LED_HALF  7

const int nMusicVisualiser = 2;
int musicVisualiser = 1;


#define esp Serial1

#define COLOR_ORDER            GRB
#define CHIPSET                WS2811

#define LED_PIN_TOP            3
#define MATRIX_WIDTH_TOP       10
#define MATRIX_HEIGHT_TOP      10
#define MATRIX_TYPE_TOP        HORIZONTAL_ZIGZAG_MATRIX

#define LED_PIN_BOTTOM         20
#define MATRIX_WIDTH_BOTTOM    40
#define MATRIX_HEIGHT_BOTTOM   10
#define MATRIX_TYPE_BOTTOM     HORIZONTAL_MATRIX

cLEDMatrix < -MATRIX_WIDTH_TOP, MATRIX_HEIGHT_TOP, MATRIX_TYPE_TOP > matrix_top;
cLEDMatrix < -MATRIX_WIDTH_BOTTOM, MATRIX_HEIGHT_BOTTOM, MATRIX_TYPE_BOTTOM > matrix_bottom;


//SNAKE
//wie lang kann die Snake maximal werden
const int anzahlPixel = 500;
byte SnakeItems = 0;
byte oldSnakeItems = 0;
//groesse des Spielfeldes
#define BaneGridXmax 50
#define BaneGridYmax 10
//Snakeitems
byte oldSnakeItemPosX[anzahlPixel];
byte oldSnakeItemPosY[anzahlPixel];
byte SnakeItemPosX[anzahlPixel];
byte SnakeItemPosY[anzahlPixel];
//Position des Apples
byte ApplePosX;
byte ApplePosY;
//Farben der 3 Arten
#define BLANK 0
#define SNAKE 1
#define APPLE 2
//Spielfeld
byte Playfield[BaneGridXmax + 1][BaneGridYmax + 1];
byte SnakeHeadID = 0;
byte SnakeBackID = 0;
byte AppleCount = 0;
//define Richtungen
#define SNAKE_RIGHT 0
#define SNAKE_LEFT 1
#define SNAKE_UP 2
#define SNAKE_DOWN 3
//Laufrichtungen
byte movingDirection = SNAKE_RIGHT;
byte snakeDirection = SNAKE_RIGHT;
byte AddSnakeItem = 0;
//eins addieren wenn apple gefunden
byte SnakeItemsToAddAtApple = 1;
//snakespeed
#define SNAKESPEED_BEGIN 200
int snakespeed = SNAKESPEED_BEGIN;
int snakespeedmax = 20;
//spiel laeuft
boolean snakerun = false;
boolean snakeOntop = false;
unsigned long SnakeScore = 0;
unsigned long SnakeHighscore = 0;
const unsigned int ScoreEEPROMaddress = 100;



//Tetris
#define TETRIS_WIDTH 10
#define TETRIS_HIGHT 20
boolean tetrisrun = false;
int tetrisspeed = 400;
byte PlayfieldTetris[TETRIS_WIDTH][TETRIS_HIGHT];
byte Block[TETRIS_WIDTH][TETRIS_HIGHT + 2];
byte Pile[TETRIS_WIDTH][TETRIS_HIGHT];

int currentblock = 0;
int currentrotation = 0;

boolean blockflying = false;

const struct CRGB TetrisColor[] = {CRGB(0, 0, 0), CRGB(0, 255, 255), CRGB(0, 0, 255), CRGB(255, 165, 0), CRGB(255, 255, 0),
        CRGB(50, 205, 50), CRGB(255, 0, 255), CRGB(255, 0, 0), CRGB(255, 255, 255)
};
#define TETRIS_BLACK 0
#define TETRIS_WHITE 8
byte blockcolor[] = {0, 1, 2, 3, 4, 5, 6, 7};

int TetrisScore = 0;
int TetrisHighScore = 0;




boolean doSnake = false;
boolean doTetris = false;
boolean doText = false;
boolean doAnimation = false;
boolean doFire = false;
boolean doNoise = false;
boolean doMusic = false;


const int nAnimation = 4;
int animation = 1;

boolean NoisePalette = false;
int paletteH[4] = {0};
int paletteS[4] = {0};
int paletteV[4] = {0};

int wait = 50;
int Brightness = 200, Saturation = 255;

cLEDText scrollingtext;
#define MAX_BUFFER  100
unsigned char TextBuffer[MAX_BUFFER];


static uint16_t x;
static uint16_t y;
static uint16_t z;
uint16_t speed = 20;
uint16_t scale = 30;
uint8_t noise[MATRIX_WIDTH_BOTTOM + MATRIX_WIDTH_TOP][MATRIX_HEIGHT_BOTTOM];
CRGBPalette16 currentPalette( PartyColors_p );
uint8_t colorLoop = 1;
int palette = 0;
int maxpalette = 11;

CRGBPalette16 gPal;


#define SQUARE_WIDTH    7
#define SQUARE_HEIGHT   7
const uint8_t SquareData1[] =
{
  B8_2BIT(11111110),
  B8_2BIT(12222210),
  B8_2BIT(12333210),
  B8_2BIT(12333210),
  B8_2BIT(12333210),
  B8_2BIT(12222210),
  B8_2BIT(11111110)
};
const uint8_t SquareMask1[] =
{
  B8_2BIT(11111110),
  B8_2BIT(11111110),
  B8_2BIT(11111110),
  B8_2BIT(11111110),
  B8_2BIT(11111110),
  B8_2BIT(11111110),
  B8_2BIT(11111110)
};
cLEDSprites Sprites(&matrix_bottom);
#define MAX_SQUARES  8
#define NUM_COLS  3
cSprite Shape1[MAX_SQUARES];
struct CRGB ColTabs[MAX_SQUARES][NUM_COLS];
int NumSquares;


#define SHAPE_WIDTH    5
#define SHAPE_HEIGHT   5
const uint8_t ShapeData2[] =
{
  B8_1BIT(00100000),
  B8_1BIT(01110000),
  B8_1BIT(11111000),
  B8_1BIT(01110000),
  B8_1BIT(00100000),
};
struct CRGB ColTable[1] = { CRGB(64, 128, 255) };
cSprite Shape2(SHAPE_WIDTH, SHAPE_HEIGHT, ShapeData2, 1, _1BIT, ColTable, ShapeData2);

#define COOLING  55
#define SPARKING 120
bool gReverseDirection = false;

uint8_t hue;








void setup() {
  AudioMemory(48);
  Serial.begin(9600);

  delay(500);
  FastLED.addLeds<CHIPSET, LED_PIN_TOP, COLOR_ORDER>(matrix_top[0], matrix_top.Size());
  FastLED.addLeds<CHIPSET, LED_PIN_BOTTOM, COLOR_ORDER>(matrix_bottom[0], matrix_bottom.Size());
  FastLED.clear();
  FastLED.show();
  delay(500);

  gPal = HeatColors_p;

  audioShield.enable();
  audioShield.inputSelect(AUDIO_INPUT_LINEIN);
  mixerInput.gain(0, vol);
  mixerInput.gain(1, vol);
  audioShield.volume(0.5);
  fft.windowFunction(AudioWindowHanning1024);

  SPI.setMOSI(7);
  SPI.setSCK(14);

  if (!(SD.begin(10))) while (1) delay(500);

  x = random16();
  y = random16();
  z = random16();

  esp.begin(9600);
  esp.setTimeout(100);
}//END SETUP


long movingtime = millis();
long t2 = millis();
long lev = millis();


void loop() {
  random16_add_entropy( random());

  if (doText) Text();
  if (doSnake) Snake();
  if (doAnimation) Animation(animation);
  if (doFire) Fire();
  if (doNoise) Noise();
  if (doMusic) Music();
  if (doTetris) Tetris();

  if (esp.available() > 0) {
    String in = "";
    while (esp.available()) {
      in += char(esp.read());
      delay(10);
    }
    handleIncoming(in);
  }
}//END LOOP


void handleIncoming(String incoming) {
  if (incoming.indexOf(/*IP*/) > -1) {
    //ESP has start
  }

  if (incoming.indexOf("Animation") > -1) {
    if (doAnimation) {
      Sprites.RemoveAllSprites();
      increaseMode(animation, nAnimation);
    }
    else {
      resetDos();
      doAnimation = true;
    }
    InitAnimation(animation);
  }

  if (incoming.indexOf("Fire") > -1) {
    resetDos();
    doFire = true;
  }

  if (incoming.indexOf("Noise") > -1) {
    if (doNoise) {
      ChangePaletteAndSettings();
    }
    else {
      resetDos();
      doNoise = true;
    }
  }
  if (incoming.indexOf("NoisePalette") > -1) {
    if (doNoise) {
      NoisePalette = true;
    }
  }

  if (incoming.indexOf("Music") > -1) {
    if (doMusic) {
      increaseMode(musicVisualiser, nMusicVisualiser);
    }
    else {
      resetDos();
      doMusic = true;
    }
  }

  if (incoming.indexOf("Volume") > -1) {
    int v = map(getValue(incoming), 0, 100, 10, 1);
    vol = 1.0 / ((float) v);
    mixerInput.gain(0, vol);
    mixerInput.gain(1, vol);
  }

  if (incoming.indexOf("Text") > -1) {
    StringToTextBuffer(getText(incoming), 0);
    resetDos();
    doText = true;
  }

  if (incoming.indexOf("Saturation") > -1) {
    Saturation = map(getValue(incoming), 0, 100, 0, 255);
  }

  if (incoming.indexOf("Brightness") > -1) {
    Brightness = map(getValue(incoming), 0, 100, 0, 255);
    FastLED.setBrightness(Brightness);
    FastLED.show();
  }

  if (incoming.indexOf("Speed") > -1) {
    wait = map(getValue(incoming), 0, 100, 100, 0);
  }

  if (incoming.indexOf("Aus") > -1) {
    Sprites.RemoveAllSprites();
    resetDos();
  }

  handleNoise(incoming);

  handleGame(incoming);

}//end handleIncoming()

void handleGame(String in) {
  if (doSnake) handleSnake(in);
  else if (doTetris) handleTetris(in);

  if (in.indexOf("SnakeTop") > -1) {
    resetDos();
    doSnake = true;
    NewGameSnake();
  }
  if (in.indexOf("TetrisTop") > -1) {
    resetDos();
    doTetris = true;
    newGameTetris();
  }
}//end handleGame()


void resetDos() {
  FastLED.clear();
  FastLED.show();
  doSnake = false;
  snakerun = false;
  doText = false;
  doAnimation = false;
  doFire = false;
  doNoise = false;
  doMusic = false;
  doTetris = false;
  NoisePalette = false;
}//end resetDos()

String getText(String str) {
  int sep1 = str.indexOf("-");
  int endsep = str.indexOf("-", sep1 + 1);
  return str.substring(sep1 + 1, endsep);
}//end Text()

int getValue(String str) {
  int sep1 = str.indexOf("-");
  int endsep = str.indexOf("-", sep1 + 1);
  return (str.substring(sep1 + 1, endsep)).toInt();
}//end int getValue()

void getValue(String str, int &r, int &g, int &b) {
  int sep1 = str.indexOf("-");
  int sep2 = str.indexOf("-", sep1 + 1);
  int sep3 = str.indexOf("-", sep2 + 1);
  int endsep = str.indexOf("-", sep3 + 1);
  r = (str.substring(sep1 + 1, sep2)).toInt();
  g = (str.substring(sep2 + 1, sep3)).toInt();
  b = (str.substring(sep3 + 1, endsep)).toInt();
}//end getValue()

void increaseMode(int &mode, int max) {
  mode = mode + 1;
  if (mode > max) mode = 1;
}//end increase()

void EEPROMWritelong(int address, long value) {
  //Decomposition from a long to 4 bytes by using bitshift.
  //One = Most significant -> Four = Least significant byte
  byte four = (value & 0xFF);
  byte three = ((value >> 8) & 0xFF);
  byte two = ((value >> 16) & 0xFF);
  byte one = ((value >> 24) & 0xFF);
  //Write the 4 bytes into the eeprom memory.
  EEPROM.write(address, four);
  EEPROM.write(address + 1, three);
  EEPROM.write(address + 2, two);
  EEPROM.write(address + 3, one);
}//end EEPROMWritelong()

long EEPROMReadlong(long address) {
  //Read the 4 bytes from the eeprom memory.
  long four = EEPROM.read(address);
  long three = EEPROM.read(address + 1);
  long two = EEPROM.read(address + 2);
  long one = EEPROM.read(address + 3);
  //Return the recomposed long by using bitshift.
  return ((four << 0) & 0xFF) + ((three << 8) & 0xFFFF) + ((two << 16) & 0xFFFFFF) + ((one << 24) & 0xFFFFFFFF);
}//end EEPROMReadlong()

void playEffect(int i) {
  if (playeffects && i < nSoundeffects) {
    wavEffects.play(soundeffects[i]);
    delay(10);
  }
}//end playEffects()






void Animation(int animation) {
  int16_t sx, sy, x, y;
  uint8_t h;

  if (millis() > t2 + wait) {
    switch (animation) {
      case 1:
        FastLED.clear();
        Sprites.UpdateSprites();
        for (int i = 0; i < NumSquares; i++) {
          if (Shape1[i].GetFlags() & SPRITE_EDGE_X_MAX)
            Sprites.ChangePriority(&Shape1[i], SPR_BACK);
          else if (Shape1[i].GetFlags() & SPRITE_EDGE_X_MIN)
            Sprites.ChangePriority(&Shape1[i], SPR_FRONT);
        }
        Sprites.RenderSprites();
        FastLED.show();
        break;
      case 2:
        FastLED.clear();
        Sprites.UpdateSprites();
        Sprites.RenderSprites();
        FastLED.show();
        break;
      case 3:
        FastLED.clear();
        h = hue;
        for (x = 0; x < (matrix_bottom.Width() + matrix_bottom.Height()); ++x) {
          matrix_bottom.DrawLine(x - matrix_bottom.Height(), matrix_bottom.Height() - 1, x, 0, CHSV(h, Saturation, Brightness));
          matrix_top.DrawFilledRectangle(0, 0, 9, 9, CHSV(h, Saturation, Brightness));
          h += 16;
        }
        hue += 4;
        FastLED.show();
        break;
      case 4:
        FastLED.clear();
        h = hue;
        for (y = 0; y < matrix_bottom.Height(); ++y) {
          matrix_bottom.DrawLine(0, y, matrix_bottom.Width() - 1, y, CHSV(h, Saturation, Brightness));
          h += 16;
        }
        for (y = 0; y < matrix_top.Width() / 2; ++y) {
          matrix_top.DrawRectangle(y, y, matrix_top.Width() - y - 1, matrix_top.Height() - y - 1, CHSV(h, Saturation, Brightness));
          h += 16;
        }
        hue += 4;
        FastLED.show();
        break;
    }
    t2 = millis();
  }
}//end Animation()

void InitAnimation(int animaiton) {
  if (animation == 1) {
    int16_t x = MATRIX_WIDTH_BOTTOM - SQUARE_WIDTH - 1, y = 0;
    int8_t xc = -1, yc = 1;
    NumSquares = 0;
    while ((NumSquares < MAX_SQUARES) && (x < (MATRIX_WIDTH_BOTTOM - SQUARE_WIDTH))) {
      for (int i = 0; i < NUM_COLS; i++)
        ColTabs[NumSquares][i] = CHSV(NumSquares * 32, 255, 127 + (i * 64));
      Shape1[NumSquares].Setup(SQUARE_WIDTH, SQUARE_HEIGHT, SquareData1, 1, _2BIT, ColTabs[NumSquares], SquareMask1);
      Shape1[NumSquares].SetPositionFrameMotionOptions(x, y, 0, 0, xc, 2, 0, 0, SPRITE_DETECT_EDGE | SPRITE_X_KEEPIN);
      Sprites.AddSprite(&Shape1[NumSquares]);
      ++NumSquares;
      x += (((SQUARE_WIDTH * 5) / 3) * xc);
      if (x <= 0) {
        x = abs(x);
        xc = +1;
      }
      y += yc;
      if ( (y == 0) || (y == (MATRIX_HEIGHT_BOTTOM - SQUARE_HEIGHT)) )
        yc = 0 - yc;
    }
  }
  else if (animation == 2) {
    Shape2.SetPositionFrameMotionOptions(0/*X*/, 0/*Y*/, 0/*Frame*/, 0/*FrameRate*/, +1/*XChange*/, 1/*XRate*/, +1/*YChange*/, 1/*YRate*/, SPRITE_DETECT_EDGE | SPRITE_X_KEEPIN | SPRITE_Y_KEEPIN);
    Sprites.AddSprite(&Shape2);
  }
  else if (animation == 3 || animation == 4) {
    hue = 0;
  }
}//end InitAnimation()

void Fire() {
  if (millis() > t2 + wait) {
    Fire2012WithPalette();
    FastLED.show();
    t2 = millis();
  }
}//end Fire()

void Fire2012WithPalette() {
  // Array of temperature readings at each simulation cell
  static byte heat[MATRIX_WIDTH_BOTTOM][MATRIX_HEIGHT_BOTTOM];

  // Step 1.  Cool down every cell a little
  for ( int i = 0; i < MATRIX_WIDTH_BOTTOM; i++) {
    for ( int j = 0; j < MATRIX_HEIGHT_BOTTOM; j++) {
      heat[i][j] = qsub8( heat[i][j],  random8(0, ((COOLING * 10) / MATRIX_HEIGHT_BOTTOM) + 2));
    }
  }

  // Step 2.  Heat from each cell drifts 'up' and diffuses a little
  for ( int i = 0; i < MATRIX_WIDTH_BOTTOM; i++) {
    for ( int k = MATRIX_HEIGHT_BOTTOM - 1; k >= 2; k--) {
      heat[i][k] = (heat[i][k - 1] + heat[i][k - 2] + heat[i][k - 2] ) / 3;
    }
  }

  // Step 3.  Randomly ignite new 'sparks' of heat near the bottom
  for ( int i = 0; i < MATRIX_WIDTH_BOTTOM; i++) {
    if ( random8() < SPARKING ) {
      int x = random8(40);
      int y = random8(2);
      heat[x][y] = qadd8( heat[x][y], random8(160, 255) );
    }
  }

  // Step 4.  Map from heat cells to LED colors
  for ( int i = 0; i < MATRIX_WIDTH_BOTTOM; i++) {
    for ( int j = 0; j < MATRIX_HEIGHT_BOTTOM; j++) {
      // Scale the heat value from 0-255 down to 0-240
      // for best results with color palettes.
      byte colorindex = scale8(heat[i][j], 240);
      CRGB color = ColorFromPalette( gPal, colorindex);
      int pixelnumber;
      if ( gReverseDirection ) {
        pixelnumber = (MATRIX_WIDTH_BOTTOM - 1) - j;
      } else {
        pixelnumber = j;
      }
      matrix_bottom(i, pixelnumber) = color;
    }
  }
}//end Fire2012WithPalette()


void Noise() {
  if (millis() > t2 + wait) {
    fillnoise8();
    mapNoiseToLEDsUsingPalette();
    FastLED.show();
    t2 = millis();
  }
}//end Noise()

void fillnoise8() {
  //file in NoisePaletteDemo in FastLED examples
  uint8_t dataSmoothing = 0;
  if ( speed < 50) {
    dataSmoothing = 200 - (speed * 4);
  }

  for (int i = 0; i < MATRIX_WIDTH_BOTTOM + MATRIX_WIDTH_TOP; i++) {
    int ioffset = scale * i;
    for (int j = 0; j < MATRIX_HEIGHT_BOTTOM; j++) {
      int joffset = scale * j;

      uint8_t data = inoise8(x + ioffset, y + joffset, z);
      data = qsub8(data, 16);
      data = qadd8(data, scale8(data, 39));

      if ( dataSmoothing ) {
        uint8_t olddata = noise[i][j];
        uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing);
        data = newdata;
      }
      noise[i][j] = data;
    }
  }

  z += speed;

  x += speed / 8;
  y -= speed / 16;
}//end fillnoise8()

void mapNoiseToLEDsUsingPalette() {
  static uint8_t ihue = 0;

  for (int i = 0; i < MATRIX_WIDTH_BOTTOM + MATRIX_WIDTH_TOP; i++) {
    for (int j = 0; j < MATRIX_HEIGHT_BOTTOM; j++) {
      uint8_t index = noise[j][i];
      uint8_t bri =   noise[i][j];

      if ( colorLoop) {
        index += ihue;
      }

      if ( bri > 127 ) {
        bri = 255;
      } else {
        bri = dim8_raw( bri * 2);
      }
      CRGB color1 = ColorFromPalette( currentPalette, index, bri);

      if (i < MATRIX_WIDTH_BOTTOM) matrix_bottom(i, j) = color1;
      else matrix_top(i - MATRIX_WIDTH_BOTTOM, j) = color1;
    }
  }
  ihue += 1;
}//end mapNoiseToLEDsUsingPalette()

void ChangePaletteAndSettings() {
  palette++;
  if (palette > maxpalette) palette = 0;

  if (palette ==  0)  {
    currentPalette = RainbowColors_p;
    speed = 20;
    scale = 30;
    colorLoop = 1;
  }
  if (palette == 1) {
    SetupPurpleAndGreenPalette();
    speed = 10;
    scale = 50;
    colorLoop = 1;
  }
  if (palette == 2) {
    SetupBlackAndWhiteStripedPalette();
    speed = 20;
    scale = 30;
    colorLoop = 1;
  }
  if (palette == 3) {
    currentPalette = ForestColors_p;
    speed =  8;
    scale = 120;
    colorLoop = 0;
  }
  if (palette == 4) {
    currentPalette = CloudColors_p;
    speed =  4;
    scale = 30;
    colorLoop = 0;
  }
  if (palette == 5) {
    currentPalette = LavaColors_p;
    speed =  8;
    scale = 50;
    colorLoop = 0;
  }
  if (palette == 6) {
    currentPalette = OceanColors_p;
    speed = 20;
    scale = 90;
    colorLoop = 0;
  }
  if (palette == 7) {
    currentPalette = PartyColors_p;
    speed = 20;
    scale = 30;
    colorLoop = 1;
  }
  if (palette == 8) {
    SetupRandomPalette();
    speed = 20;
    scale = 20;
    colorLoop = 1;
  }
  if (palette == 9) {
    SetupRandomPalette();
    speed = 50;
    scale = 50;
    colorLoop = 1;
  }
  if (palette == 10) {
    SetupRandomPalette();
    speed = 90;
    scale = 90;
    colorLoop = 1;
  }
  if (palette == 11) {
    currentPalette = RainbowStripeColors_p;
    speed = 30;
    scale = 20;
    colorLoop = 1;
  }
}//end ChangePaletteAndSettingsPeriodically()

void SetupRandomPalette() {
  currentPalette = CRGBPalette16(
                     CHSV( random8(), 255, 32),
                     CHSV( random8(), 255, 255),
                     CHSV( random8(), 128, 255),
                     CHSV( random8(), 255, 255));
}//end SetupRandomPalette()

void SetupBlackAndWhiteStripedPalette() {
  fill_solid( currentPalette, 16, CRGB::Black);
  currentPalette[0] = CRGB::White;
  currentPalette[4] = CRGB::White;
  currentPalette[8] = CRGB::White;
  currentPalette[12] = CRGB::White;

}//end SetupBlackAndWhiteStripedPalette()

void SetupPurpleAndGreenPalette() {
  CRGB purple = CHSV( HUE_PURPLE, 255, 255);
  CRGB green  = CHSV( HUE_GREEN, 255, 255);
  CRGB black  = CRGB::Black;

  currentPalette = CRGBPalette16(
                     green,  green,  black,  black,
                     purple, purple, black,  black,
                     green,  green,  black,  black,
                     purple, purple, black,  black );
}//end SetupPurpleAndGreenPalette()


void handleNoise(String incoming) {
  if (doNoise && NoisePalette) {
    if (incoming.indexOf("PaletteH0") > -1) paletteH[0] = getValue(incoming);
    if (incoming.indexOf("PaletteH1") > -1) paletteH[1] = getValue(incoming);
    if (incoming.indexOf("PaletteH2") > -1) paletteH[2] = getValue(incoming);
    if (incoming.indexOf("PaletteH3") > -1) paletteH[3] = getValue(incoming);
    if (incoming.indexOf("PaletteS0") > -1) paletteS[0] = getValue(incoming);
    if (incoming.indexOf("PaletteS1") > -1) paletteS[1] = getValue(incoming);
    if (incoming.indexOf("PaletteS2") > -1) paletteS[2] = getValue(incoming);
    if (incoming.indexOf("PaletteS3") > -1) paletteS[3] = getValue(incoming);
    if (incoming.indexOf("PaletteV0") > -1) paletteV[0] = getValue(incoming);
    if (incoming.indexOf("PaletteV1") > -1) paletteV[1] = getValue(incoming);
    if (incoming.indexOf("PaletteV2") > -1) paletteV[2] = getValue(incoming);
    if (incoming.indexOf("PaletteV3") > -1) paletteV[3] = getValue(incoming);

    if (incoming.indexOf("PaletteSpeed") > -1) speed = getValue(incoming);
    if (incoming.indexOf("PaletteScale") > -1) scale = getValue(incoming);
    if (incoming.indexOf("PaletteLoop") > -1) colorLoop = (colorLoop ? 0 : 1);

    if (NoisePalette) {
      currentPalette = CRGBPalette16(CHSV(paletteH[0], paletteS[0], paletteV[0]),
                                     CHSV(paletteH[1], paletteS[1], paletteV[1]),
                                     CHSV(paletteH[2], paletteS[2], paletteV[2]),
                                     CHSV(paletteH[3], paletteS[3], paletteV[3]));
    }
  }
}//end handleNoise()

void Music() {
  if (musicVisualiser == 1) Music1();
  if (musicVisualiser == 2) Music2();
}//end Music()

void Music1() {
  if (fft.available()) {
    double a = 2, b = 0;
    fft_values[0] = fft.read(0);
    fft_values[1] = fft.read(1);
    for (int i = 2; i < 40; i++) {
      if (i < 10) b = a + a / 3;
      else if (i < 20) b = a + a / 6;
      else b = a + a / 8;
      fft_values[i] = fft.read((int) a, (int) b);
      a = b;
    }
  }

  if (peak1.available()) {
    peak = peak1.read() * 25;
  }

  if (millis() > t2 + 30) {
    FastLED.clear();
    for (int i = 0; i < 40; i++) {
      int z = min(db(fft_values[i] * 100.0), 20);
      if (z > fft_show[i]) fft_show[i] = z;
      else if (fft_show[i] > 0) fft_show[i]--;

      for (int x = 0; x < map(fft_show[i], 0, 20, 0, 10); x++) {
        CRGB color = ColorFromPalette(PartyColors_p, x * 12);
        matrix_bottom(i, x) = color;
      }
    }

    if (peak > peak_show + 3 && peak > 5) {
      peak_show = peak;
      xx1 = random(0, 8);
      yy1 = random(0, 8);
      bb1 = random(0, 255);
    }
    else if (peak_show > 0) {
      peak_show--;
    }
    matrix_top.DrawRectangle(xx1, yy1, xx1 + 1, yy1 + 1, CHSV(bb1, 200, peak_show * 10));

    FastLED.show();
    t2 = millis();
  }
}//end Music1()



void Music2() {
  if (millis() > t2 + wait) {
    if (peak1.available()) {
      volume = peak1.read() * 30.0;
      avgVol = (avgVol + volume) / 2.0;

      if (volume < avgVol / 2.0 || volume < 15) volume = 0;

      if (volume > maxVol) maxVol = volume;

      if (gradient > 1529) {
        gradient %= 1530;
        maxVol = (maxVol + volume) / 2.0;
      }

      if (volume - last > avgVol - last && avgVol - last > 0) avgBump = (avgBump + (volume - last)) / 2.0;
      bump = (volume - last) > avgBump;

      Pulse();

      gradient++;

      last = volume;
    }
    t2 = millis();
  }
}//end Music2()

void Pulse() {
  fade(0.85);

  if (bump) gradient += 64;

  if (volume > 0) {
    CRGB color1 = ColorFromPalette( currentPalette, gradient % 255);

    int start = LED_HALF - (LED_HALF * (volume / maxVol));
    int finish = LED_HALF + (LED_HALF * (volume / maxVol));

    for (int i = start; i < finish; i++) {
      float damp = float(((finish - start) / 2.0) - abs((i - start) - ((finish - start) / 2.0))) / float((finish - start) / 2.0);

      if (i < 10)
        matrix_bottom.DrawLine(0, i, matrix_bottom.Width() - 1, i,
                               CRGB(color1.r * pow(damp, 2.0) * vol, color1.g * pow(damp, 2.0) * vol, color1.b * pow(damp, 2.0) * vol));
      else
        matrix_top.DrawRectangle(i - 10, i - 10, 19 - i, 19 - i,
                                 CRGB(color1.r * pow(damp, 2.0) * vol, color1.g * pow(damp, 2.0) * vol, color1.b * pow(damp, 2.0) * vol));
    }
    FastLED.setBrightness(255.0 * pow(volume / maxVol, 2));
  }

  FastLED.show();
}

void fade(float damper) {
  if (damper >= 1) damper = 0.99;

  for (int i = 0; i < MATRIX_WIDTH_BOTTOM; i++) {
    for (int j = 0; j < MATRIX_HEIGHT_BOTTOM; j++) {
      matrix_bottom(i, j).r = matrix_bottom(i, j).r * damper;
      matrix_bottom(i, j).g = matrix_bottom(i, j).g * damper;
      matrix_bottom(i, j).b = matrix_bottom(i, j).b * damper;
    }
  }

  for (int i = 0; i < MATRIX_WIDTH_TOP; i++) {
    for (int j = 0; j < MATRIX_HEIGHT_TOP; j++) {
      matrix_top(i, j).r = matrix_top(i, j).r * damper;
      matrix_top(i, j).g = matrix_top(i, j).g * damper;
      matrix_top(i, j).b = matrix_top(i, j).b * damper;
    }
  }
}

float db(float n) {
  if (n <= 0) return 0;  // or whatever you consider to be "off"
  return log10f(n) * 20.0f;
}//end db()





void Tetris() {
  if (millis() > movingtime + tetrisspeed && tetrisrun) {
    moveDown();
    movingtime = millis();
  }
}//end Tetris()

void handleTetris(String incoming) {
  if (incoming.indexOf("GameStart") > -1) {
    tetrisrun = true;
  }
  if (incoming.indexOf("GamePause") > -1) {
    tetrisrun = false;
  }
  if (incoming.indexOf("GameRotate") > -1) {
    if (tetrisrun) rotateBlock();
  }
  if (incoming.indexOf("GameLeft") > -1) {
    if (tetrisrun) moveLeft(true);
  }
  if (incoming.indexOf("GameRight") > -1) {
    if (tetrisrun) moveRight(true);
  }
  if (incoming.indexOf("GameDown") > -1) {
    if (tetrisrun) moveDown();
  }
}//end handleTetris()

void newGameTetris() {
  tetrisrun = false;
  removePile();
  newBlock();
  drawBorder();
  updatePlayfield();
}//end newGame()

void removePile() {
  for (int i = 0; i < TETRIS_WIDTH; i++) {
    for (int j = 0; j < TETRIS_HIGHT; j++) {
      Pile[i][j] = TETRIS_BLACK;
    }
  }
}//end removePile()

void newBlock() {

  for (int i = 0; i < TETRIS_WIDTH; i++) {
    for (int j = 0; j < TETRIS_HIGHT; j++) {
      Block[i][j] = TETRIS_BLACK;
    }
  }

  removeFullRow();

  currentblock = random(7);
  currentrotation = 0;

  if (currentblock == 0) {
    //generate I
    Block[3][0] = blockcolor[currentblock + 1];
    Block[4][0] = blockcolor[currentblock + 1];
    Block[5][0] = blockcolor[currentblock + 1];
    Block[6][0] = blockcolor[currentblock + 1];
  }
  if (currentblock == 1) {
    //generate J
    Block[3][0] = blockcolor[currentblock + 1];
    Block[3][1] = blockcolor[currentblock + 1];
    Block[4][1] = blockcolor[currentblock + 1];
    Block[5][1] = blockcolor[currentblock + 1];
  }
  if (currentblock == 2) {
    //generate L
    Block[5][0] = blockcolor[currentblock + 1];
    Block[5][1] = blockcolor[currentblock + 1];
    Block[4][1] = blockcolor[currentblock + 1];
    Block[3][1] = blockcolor[currentblock + 1];
  }
  if (currentblock == 3) {
    //generate O
    Block[3][0] = blockcolor[currentblock + 1];
    Block[4][0] = blockcolor[currentblock + 1];
    Block[3][1] = blockcolor[currentblock + 1];
    Block[4][1] = blockcolor[currentblock + 1];
  }
  if (currentblock == 4) {
    //generate S
    Block[4][0] = blockcolor[currentblock + 1];
    Block[5][0] = blockcolor[currentblock + 1];
    Block[4][1] = blockcolor[currentblock + 1];
    Block[3][1] = blockcolor[currentblock + 1];
  }
  if (currentblock == 5) {
    //generate Z
    Block[3][0] = blockcolor[currentblock + 1];
    Block[4][0] = blockcolor[currentblock + 1];
    Block[4][1] = blockcolor[currentblock + 1];
    Block[5][1] = blockcolor[currentblock + 1];
  }
  if (currentblock == 6) {
    //generate T
    Block[4][0] = blockcolor[currentblock + 1];
    Block[3][1] = blockcolor[currentblock + 1];
    Block[4][1] = blockcolor[currentblock + 1];
    Block[5][1] = blockcolor[currentblock + 1];
  }

  for (int j = 0; j < TETRIS_HIGHT; j++) {
    for (int i = 0; i < TETRIS_WIDTH - 1; i++) {
      if (Block[i][j] != TETRIS_BLACK && Pile[i][j] != TETRIS_BLACK) {
        GameOverTetris();
        return;
      }
    }
  }

}//end newBlock()

void rotateBlock() {
  if (currentblock == 3) return;

  int xx, yy;

  for (int i = TETRIS_WIDTH - 1; i >= 0; i--) {
    for (int j = 0; j < TETRIS_HIGHT; j++) {
      if (Block[i][j] != TETRIS_BLACK) xx = i;
    }
  }

  for (int j = TETRIS_HIGHT - 1; j >= 0; j--) {
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      if (Block[i][j] != TETRIS_BLACK) yy = j;
    }
  }

  if (currentblock == 0) {
    if (currentrotation == 0 || currentrotation == 2) {
      Block[xx][yy] = TETRIS_BLACK;
      Block[xx + 2][yy] = TETRIS_BLACK;
      Block[xx + 3][yy] = TETRIS_BLACK;

      Block[xx + 1][yy - 1] = blockcolor[currentblock + 1];
      Block[xx + 1][yy + 1] = blockcolor[currentblock + 1];
      Block[xx + 1][yy + 2] = blockcolor[currentblock + 1];
    }
    if (currentrotation == 1 || currentrotation == 3) {
      if (!spaceLeft()) {
        if (spaceRight3()) {
          if (!moveRight(false))
            return;
          xx++;;
        }
        else {
          return;
        }
      }
      else if (!spaceRight()) {
        if (spaceLeft3()) {
          if (!moveLeft(false))
            return;
          if (!moveLeft(false))
            return;
          xx--;
          xx--;
        }
        else {
          return;
        }
      }
      else if (!spaceRight2()) {
        if (spaceLeft2()) {
          if (!moveLeft(false))
            return;
          xx--;
        }
        else {
          return;
        }
      }
      Block[xx][yy] = TETRIS_BLACK;
      Block[xx][yy + 2] = TETRIS_BLACK;
      Block[xx][yy + 3] = TETRIS_BLACK;

      Block[xx - 1][yy + 1] = blockcolor[currentblock + 1];
      Block[xx + 1][yy + 1] = blockcolor[currentblock + 1];
      Block[xx + 2][yy + 1] = blockcolor[currentblock + 1];
    }
  }

  xx++;
  yy++;

  if (currentblock == 1) {
    if (currentrotation == 0) {
      Block[xx - 1][yy - 1] = TETRIS_BLACK;
      Block[xx - 1][yy] = TETRIS_BLACK;
      Block[xx + 1][yy] = TETRIS_BLACK;

      Block[xx][yy - 1] = blockcolor[currentblock + 1];
      Block[xx + 1][yy - 1] = blockcolor[currentblock + 1];
      Block[xx][yy + 1] = blockcolor[currentblock + 1];
    }
    if (currentrotation == 1) {
      if (!spaceLeft()) {
        if (!moveRight(false))
          return;
        xx++;
      }
      xx--;

      Block[xx][yy - 1] = TETRIS_BLACK;
      Block[xx + 1][yy - 1] = TETRIS_BLACK;
      Block[xx][yy + 1] = TETRIS_BLACK;

      Block[xx - 1][yy] = blockcolor[currentblock + 1];
      Block[xx + 1][yy] = blockcolor[currentblock + 1];
      Block[xx + 1][yy + 1] = blockcolor[currentblock + 1];
    }
    if (currentrotation == 2) {
      yy--;

      Block[xx - 1][yy] = TETRIS_BLACK;
      Block[xx + 1][yy] = TETRIS_BLACK;
      Block[xx + 1][yy + 1] = TETRIS_BLACK;

      Block[xx][yy - 1] = blockcolor[currentblock + 1];
      Block[xx][yy + 1] = blockcolor[currentblock + 1];
      Block[xx - 1][yy + 1] = blockcolor[currentblock + 1];
    }
    if (currentrotation == 3) {
      if (!spaceRight()) {
        if (!moveLeft(false))
          return;
        xx--;
      }
      Block[xx][yy - 1] = TETRIS_BLACK;
      Block[xx][yy + 1] = TETRIS_BLACK;
      Block[xx - 1][yy + 1] = TETRIS_BLACK;

      Block[xx - 1][yy - 1] = blockcolor[currentblock + 1];
      Block[xx - 1][yy] = blockcolor[currentblock + 1];
      Block[xx + 1][yy] = blockcolor[currentblock + 1];
    }
  }

  if (currentblock == 2) {
    if (currentrotation == 0) {
      Block[xx + 1][yy - 1] = TETRIS_BLACK;
      Block[xx - 1][yy] = TETRIS_BLACK;
      Block[xx + 1][yy] = TETRIS_BLACK;

      Block[xx][yy - 1] = blockcolor[currentblock + 1];
      Block[xx + 1][yy + 1] = blockcolor[currentblock + 1];
      Block[xx][yy + 1] = blockcolor[currentblock + 1];
    }
    if (currentrotation == 1) {
      if (!spaceLeft()) {
        if (!moveRight(false))
          return;
        xx++;
      }
      xx--;

      Block[xx][yy - 1] = TETRIS_BLACK;
      Block[xx + 1][yy + 1] = TETRIS_BLACK;
      Block[xx][yy + 1] = TETRIS_BLACK;

      Block[xx - 1][yy] = blockcolor[currentblock + 1];
      Block[xx + 1][yy] = blockcolor[currentblock + 1];
      Block[xx - 1][yy + 1] = blockcolor[currentblock + 1];
    }
    if (currentrotation == 2) {
      yy--;

      Block[xx - 1][yy] = TETRIS_BLACK;
      Block[xx + 1][yy] = TETRIS_BLACK;
      Block[xx - 1][yy + 1] = TETRIS_BLACK;

      Block[xx][yy - 1] = blockcolor[currentblock + 1];
      Block[xx][yy + 1] = blockcolor[currentblock + 1];
      Block[xx - 1][yy - 1] = blockcolor[currentblock + 1];
    }
    if (currentrotation == 3) {
      if (!spaceRight()) {
        if (!moveLeft(false))
          return;
        xx--;
      }
      Block[xx][yy - 1] = TETRIS_BLACK;
      Block[xx][yy + 1] = TETRIS_BLACK;
      Block[xx - 1][yy - 1] = TETRIS_BLACK;

      Block[xx + 1][yy - 1] = blockcolor[currentblock + 1];
      Block[xx - 1][yy] = blockcolor[currentblock + 1];
      Block[xx + 1][yy] = blockcolor[currentblock + 1];
    }
  }

  if (currentblock == 4) {
    if (currentrotation == 0 || currentrotation == 2) {
      Block[xx + 1][yy - 1] = TETRIS_BLACK;
      Block[xx - 1][yy] = TETRIS_BLACK;

      Block[xx + 1][yy] = blockcolor[currentblock + 1];
      Block[xx + 1][yy + 1] = blockcolor[currentblock + 1];
    }
    if (currentrotation == 1 || currentrotation == 3) {
      if (!spaceLeft()) {
        if (!moveRight(false))
          return;
        xx++;
      }
      xx--;

      Block[xx + 1][yy] = TETRIS_BLACK;
      Block[xx + 1][yy + 1] = TETRIS_BLACK;

      Block[xx - 1][yy] = blockcolor[currentblock + 1];
      Block[xx + 1][yy - 1] = blockcolor[currentblock + 1];
    }
  }

  if (currentblock == 5) {
    if (currentrotation == 0 || currentrotation == 2) {
      Block[xx - 1][yy - 1] = TETRIS_BLACK;
      Block[xx][yy - 1] = TETRIS_BLACK;

      Block[xx + 1][yy - 1] = blockcolor[currentblock + 1];
      Block[xx][yy + 1] = blockcolor[currentblock + 1];

    }
    if (currentrotation == 1 || currentrotation == 3) {
      if (!spaceLeft()) {
        if (!moveRight(false))
          return;
        xx++;
      }
      xx--;

      Block[xx + 1][yy - 1] = TETRIS_BLACK;
      Block[xx][yy + 1] = TETRIS_BLACK;

      Block[xx - 1][yy - 1] = blockcolor[currentblock + 1];
      Block[xx][yy - 1] = blockcolor[currentblock + 1];
    }
  }

  if (currentblock == 6) {
    if (currentrotation == 0) {
      Block[xx][yy - 1] = TETRIS_BLACK;
      Block[xx - 1][yy] = TETRIS_BLACK;
      Block[xx + 1][yy] = TETRIS_BLACK;

      Block[xx][yy - 1] = blockcolor[currentblock + 1];
      Block[xx + 1][yy] = blockcolor[currentblock + 1];
      Block[xx][yy + 1] = blockcolor[currentblock + 1];
    }
    if (currentrotation == 1) {
      if (!spaceLeft()) {
        if (!moveRight(false))
          return;
        xx++;
      }
      xx--;

      Block[xx][yy - 1] = TETRIS_BLACK;
      Block[xx + 1][yy] = TETRIS_BLACK;
      Block[xx][yy + 1] = TETRIS_BLACK;

      Block[xx - 1][yy] = blockcolor[currentblock + 1];
      Block[xx + 1][yy] = blockcolor[currentblock + 1];
      Block[xx][yy + 1] = blockcolor[currentblock + 1];
    }
    if (currentrotation == 2) {
      yy--;

      Block[xx - 1][yy] = TETRIS_BLACK;
      Block[xx + 1][yy] = TETRIS_BLACK;
      Block[xx][yy + 1] = TETRIS_BLACK;

      Block[xx][yy - 1] = blockcolor[currentblock + 1];
      Block[xx - 1][yy] = blockcolor[currentblock + 1];
      Block[xx][yy + 1] = blockcolor[currentblock + 1];
    }
    if (currentrotation == 3) {
      if (!spaceRight()) {
        if (!moveLeft(false))
          return;
        xx--;
      }
      Block[xx][yy - 1] = TETRIS_BLACK;
      Block[xx - 1][yy] = TETRIS_BLACK;
      Block[xx][yy + 1] = TETRIS_BLACK;

      Block[xx][yy - 1] = blockcolor[currentblock + 1];
      Block[xx - 1][yy] = blockcolor[currentblock + 1];
      Block[xx + 1][yy] = blockcolor[currentblock + 1];
    }
  }

  while (!checkOverlap())
  {
    for (int j = 0; j < TETRIS_HIGHT; j++)
    {
      for (int i = 0; i < TETRIS_WIDTH; i++)
      {
        Block[i][j] = Block[i][j + 1];
      }
    }
    movingtime = millis() + tetrisspeed;
  }

  currentrotation++;
  if (currentrotation == 4) currentrotation = 0;

  updatePlayfield();

}//end rotateBlock()

void moveDown() {
  if (spaceBelow()) {
    byte copyBlock[TETRIS_WIDTH][TETRIS_HIGHT];
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      for (int j = 0; j < TETRIS_HIGHT; j++) {
        copyBlock[i][j] = Block[i][j];
        Block[i][j] = TETRIS_BLACK;
      }
    }

    for (int j = TETRIS_HIGHT - 1; j > 0; j--) {
      for (int i = 0; i < TETRIS_WIDTH; i++) {
        Block[i][j] = copyBlock[i][j - 1];
      }
    }
  }
  else {
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      for (int j = 0; j < TETRIS_HIGHT; j++) {
        if (Block[i][j] != TETRIS_BLACK)
          Pile[i][j] = Block[i][j];
      }
    }
    newBlock();
  }
  updatePlayfield();
}//end moveDown

boolean moveRight(boolean refresh) {
  if (spaceRight()) {
    byte copyBlock[TETRIS_WIDTH][TETRIS_HIGHT];
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      for (int j = 0; j < TETRIS_HIGHT; j++) {
        copyBlock[i][j] = Block[i][j];
        Block[i][j] = TETRIS_BLACK;
      }
    }

    for (int i = 1; i < TETRIS_WIDTH; i++) {
      for (int j = 0; j < TETRIS_HIGHT; j++) {
        Block[i][j] = copyBlock[i - 1][j];
      }
    }
    if (refresh) updatePlayfield();
    return true;
  }
  return false;
}//end moveRight()

boolean moveLeft(boolean refresh) {
  if (spaceLeft()) {
    byte copyBlock[TETRIS_WIDTH][TETRIS_HIGHT];
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      for (int j = 0; j < TETRIS_HIGHT; j++) {
        copyBlock[i][j] = Block[i][j];
        Block[i][j] = TETRIS_BLACK;
      }
    }

    for (int i = 0; i < TETRIS_WIDTH - 1; i++) {
      for (int j = 0; j < TETRIS_HIGHT; j++) {

        Block[i][j] = copyBlock[i + 1][j];
      }
    }
    if (refresh) updatePlayfield();
    return true;
  }
  return false;
}//end moveLeft()



boolean spaceBelow() {
  for (int j = TETRIS_HIGHT - 1; j >= 0; j--) {
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      if (Block[i][j] != TETRIS_BLACK) {
        if (j == TETRIS_HIGHT - 1)
          return false;
        if (Pile[i][j + 1] != TETRIS_BLACK)
          return false;
      }
    }
  }
  return true;
}//end spaceBelow()

boolean spaceLeft() {
  for (int j = TETRIS_HIGHT - 1; j >= 0; j--) {
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      if (Block[i][j] != TETRIS_BLACK) {
        if (i == 0)
          return false;
        if (Pile[i - 1][j] != TETRIS_BLACK)
          return false;
      }
    }
  }
  return true;
}//end spaceLeft()

boolean spaceLeft2() {
  for (int j = TETRIS_HIGHT - 1; j >= 0; j--) {
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      if (Block[i][j] != TETRIS_BLACK) {
        if (i == 0 || i == 1)
          return false;
        if (Pile[i - 1][j] != TETRIS_BLACK || Pile[i - 2][j] != TETRIS_BLACK)
          return false;
      }
    }
  }
  return true;
}//end spaceLeft2()

boolean spaceLeft3() {
  for (int j = TETRIS_HIGHT - 1; j >= 0; j--) {
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      if (Block[i][j] != TETRIS_BLACK) {
        if (i == 0 || i == 1 || i == 2)
          return false;
        if (Pile[i - 1][j] != TETRIS_BLACK || Pile[i - 2][j] != TETRIS_BLACK || Pile[i - 3][j] != TETRIS_BLACK)
          return false;
      }
    }
  }
  return true;
}//end spaceLeft3()

boolean spaceRight() {
  for (int j = TETRIS_HIGHT - 1; j >= 0; j--) {
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      if (Block[i][j] != TETRIS_BLACK) {
        if (i == TETRIS_WIDTH - 1)
          return false;
        if (Pile[i + 1][j] != TETRIS_BLACK)
          return false;
      }
    }
  }
  return true;
}//end spaceRight()

boolean spaceRight2() {
  for (int j = TETRIS_HIGHT - 1; j >= 0; j--) {
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      if (Block[i][j] != TETRIS_BLACK) {
        if (i == TETRIS_WIDTH - 1 || i == TETRIS_WIDTH - 2)
          return false;
        if (Pile[i + 1][j] != TETRIS_BLACK || Pile[i + 2][j] != TETRIS_BLACK)
          return false;
      }
    }
  }
  return true;
}//end spaceRight2()

boolean spaceRight3() {
  for (int j = TETRIS_HIGHT - 1; j >= 0; j--) {
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      if (Block[i][j] != TETRIS_BLACK) {
        if (i == TETRIS_WIDTH - 1 || i == TETRIS_WIDTH - 2 || i == TETRIS_WIDTH - 3)
          return false;
        if (Pile[i + 1][j] != TETRIS_BLACK || Pile[i + 2][j] != TETRIS_BLACK || Pile[i + 3][j] != TETRIS_BLACK)
          return false;
      }
    }
  }
  return true;
}//end spaceRight3()

boolean checkOverlap() {
  for (int j = 0; j < TETRIS_HIGHT; j++) {
    for (int i = 0; i < TETRIS_WIDTH - 1; i++) {
      if (Block[i][j]) {
        if (Pile[i][j]) return false;
      }
    }
  }
  for (int j = TETRIS_HIGHT; j < TETRIS_HIGHT + 2; j++) {
    for (int i = 0; i < 7; i++) {
      if (Block[i][j]) return false;
    }
  }
  return true;
}//end checkOverlap()


void removeFullRow() {
  while (checkFullRow() != -1) {
    int fullrow = checkFullRow();

    playEffect(9);

    for (int i = 0; i < TETRIS_WIDTH; i++) {
      Pile[i][fullrow] = TETRIS_WHITE;
    }
    updatePlayfield();
    delay(100);
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      Pile[i][fullrow] = TETRIS_BLACK;
    }
    updatePlayfield();
    delay(100);
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      Pile[i][fullrow] = TETRIS_WHITE;
    }
    updatePlayfield();
    delay(100);
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      Pile[i][fullrow] = TETRIS_BLACK;
    }

    for (int j = fullrow; j >= 0; j--) {
      for (int i = 0; i < TETRIS_WIDTH; i++) {
        Pile[i][j] = Pile[i][j - 1];
      }
    }
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      Pile[i][0] = TETRIS_BLACK;
    }
  }
}//end removeFullRow()


int checkFullRow() {
  int cnt = 0;

  for (int j = 0; j < TETRIS_HIGHT; j++) {
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      if (Pile[i][j] != TETRIS_BLACK) cnt++;
    }
    if (cnt == 10) return j;
    cnt = 0;
  }
  return -1;
}//end checkFullRow()


void GameOverTetris() {
  tetrisrun = false;

  FastLED.clear();

  playEffect(2);

  StringToTextBuffer("GAME OVER", 1);
  matrix_top.DrawFilledRectangle(0, 0, 9, 9, CRGB::Red);
  while (scrollingtext.UpdateText() != -1) {
    FastLED.show();
    delay(50);
  }

  resetDos();
  doNoise = true;
}//end GameOverTetris()

void drawBorder() {
  matrix_bottom.DrawLine(9, 0, 9, 9, CRGB::Red);
  matrix_bottom.DrawLine(20, 0, 20, 9, CRGB::Red);
  matrix_bottom.DrawLine(20, 9, 29, 9, CRGB::Red);
  matrix_bottom.DrawLine(0, 9, 9, 9, CRGB::Red);
  matrix_bottom.DrawLine(30, 9, 39, 9, CRGB::Red);
}//end drawPlayfield()



void updatePlayfield() {
  FastLED.clear();
  for (int i = 0; i < TETRIS_WIDTH; i++) {
    for (int j = 0; j < TETRIS_HIGHT; j++) {
      if (Block[i][j] != TETRIS_BLACK) setXYPixelTetris(i, j, TetrisColor[Block[i][j]]);
      if (Pile[i][j] != TETRIS_BLACK) setXYPixelTetris(i, j, TetrisColor[Pile[i][j]]);
    }
  }
  drawBorder();
  FastLED.show();
  //PrintToMonitor(); //debug
}//end renderTetris()

void setXYPixelTetris(byte x, byte y, CRGB c) {
  if (y < 10)
    matrix_top(x, y) = c;
  else
    matrix_bottom(x + 10, 19 - y) = c;
}//end setXYPixel()

void PrintToMonitor() {
  for (int j = 0; j < TETRIS_HIGHT; j++) {
    for (int i = 0; i < TETRIS_WIDTH; i++) {
      if (Block[i][j] != 0) Serial.print("x");
      else Serial.print("-");
      Serial.print("\t");
    }
    Serial.println();
  }
  Serial.println();
}




void Text() {
  if (millis() > t2 + wait) {
    if (scrollingtext.UpdateText() == -1) {
      scrollingtext.SetText(TextBuffer, charlength(TextBuffer, MAX_BUFFER));
      setTextColor(0);
    }
    else
      FastLED.show();
    t2 = millis();
  }
}//end Text()

int PrepareText(String &txt) {
  txt = "        " + txt;
  return txt.length();
}//end PrepareText()

int PrepareText2(String &txt) {
  txt = txt;
  return txt.length();
}//end PrepareText2()

void StringToTextBuffer(String str, int mode) {
  clearTextBuffer();
  str.toCharArray((char*) TextBuffer, PrepareText(str) + 1);
  scrollingtext.SetFont(MatriseFontData);
  scrollingtext.Init(&matrix_bottom, matrix_bottom.Width(), scrollingtext.FontHeight() + 1, 0, 1);
  scrollingtext.SetText((unsigned char*) TextBuffer, charlength(TextBuffer, MAX_BUFFER));
  scrollingtext.SetScrollDirection(SCROLL_LEFT);
  scrollingtext.SetTextDirection(CHAR_UP);

  setTextColor(mode);
}//end StringToTextBuffer()

int charlength(unsigned char *chararray, int nmax) {
  int x = 0;
  for (int i = 0; i < nmax; i++) {
    int c = chararray[i];
    if (c != '\0') x++;
  }
  return x;
}//end charlength()

void clearTextBuffer() {
  for (int i = 0; i < MAX_BUFFER; i++)
    TextBuffer[i] = (char) 0 ;
}//end clearTextBuffer()

void setTextColor(int mode) {
  if (mode == 0) scrollingtext.SetTextColrOptions(COLR_HSV | COLR_GRAD | COLR_CHAR | COLR_HORI, random(0, 255), 250, 200, random(0, 255), 250, 200);
  else if (mode == 1) scrollingtext.SetTextColrOptions(COLR_GRAD_CV, 250, 0, 0, 250, 250, 250);
  else scrollingtext.SetTextColrOptions(COLR_GRAD_CH, 0, 0, 200, 250, 250, 250);
}//end setTextColor()





void Snake() {
  if (millis() > movingtime + snakespeed && snakerun) {
    moveSnake();
    movingtime = millis();
  }
}//end Snake

void handleSnake(String incoming) {
  if (incoming.indexOf("GameRight") > -1) {
    setSnakeDirection(1);
  }
  if (incoming.indexOf("GameLeft") > -1) {
    setSnakeDirection(-1);
  }
  if (incoming.indexOf("GameStart") > -1) {
    snakerun = true;
  }
  if (incoming.indexOf("GamePause") > -1) {
    snakerun = false;
  }
  if (incoming.indexOf("GameReset") > -1) {
    EEPROMWritelong(0, ScoreEEPROMaddress);
  }
}//end handleSnake()

void setXYPixel(byte x, byte y, CRGB c) {
  if (x <= 40)
    matrix_bottom(x - 1, y - 1) = c;
  else
    matrix_top(x - 41, y - 1) = c;
}//end setXYPixel()

void NewGameSnake() {
  byte x, y;

  SnakeItems = 1;
  SnakeHeadID = 1;
  SnakeItemPosX[1] = 1;
  SnakeItemPosY[1] = 1;
  movingDirection = SNAKE_RIGHT;
  snakeDirection = SNAKE_RIGHT;
  snakeOntop = false;
  snakerun = false;
  SnakeScore = 0;
  snakespeed = SNAKESPEED_BEGIN;

  //Spielfeld ausschalten
  for (y = 1; y <= BaneGridYmax; y++) {
    for (x = 1; x <= BaneGridXmax; x++) {
      Playfield[x][y] = BLANK;
    }
  }

  AddSnakeItem = 0;
  AppleCount = 0;
  placeRandomApple();

  render();
  movingtime = millis();
}//end newgame

void render() {
  byte i, x, y;

  for (i = 1; i <= oldSnakeItems; i++) {
    if (oldSnakeItemPosX[i] > 0 && oldSnakeItemPosY[i] > 0 && oldSnakeItemPosX[i] <= BaneGridXmax && oldSnakeItemPosY[i] <= BaneGridYmax) {
      Playfield[oldSnakeItemPosX[i]][oldSnakeItemPosY[i]] = BLANK;
    }
  }

  for (i = 1; i <= SnakeItems; i++) {
    if (SnakeItemPosX[i] > 0 && SnakeItemPosY[i] > 0 && SnakeItemPosX[i] <= BaneGridXmax && SnakeItemPosY[i] <= BaneGridYmax) {
      Playfield[SnakeItemPosX[i]][SnakeItemPosY[i]] = SNAKE;
      oldSnakeItemPosX[i] = SnakeItemPosX[i];
      oldSnakeItemPosY[i] = SnakeItemPosY[i];
    }
  }

  oldSnakeItems = SnakeItems;

  //schalte die leds auf der matrix
  for (y = 1; y <= BaneGridYmax; y++) {
    for (x = 1; x <= BaneGridXmax; x++) {
      switch (Playfield[x][y]) {
        case BLANK:
          setXYPixel(x, y, CRGB::Black); //Blank
          break;

        case SNAKE:
          if (SnakeItemPosX[SnakeHeadID] == x && SnakeItemPosY[SnakeHeadID] == y)
            setXYPixel(x, y, CRGB::Yellow); // Yellow snake head
          else
            setXYPixel(x, y, CRGB::Green); // Green snake body
          break;

        case APPLE:
          setXYPixel(x, y, CRGB::Red);  //Red Apple
          break;

        default:
          setXYPixel(x, y, CRGB::Black);  //Blank
          break;
      }
    }
  }

  FastLED.show();
}//end render


void moveSnake() {
  byte i;
  movingDirection = snakeDirection;

  if (AddSnakeItem == 0) {
    //wenn er keinen apfel isst
    SnakeBackID = SnakeHeadID - 1;

    if (SnakeBackID == 0) SnakeBackID = SnakeItems;

    SnakeItemPosX[SnakeBackID] = SnakeItemPosX[SnakeHeadID];
    SnakeItemPosY[SnakeBackID] = SnakeItemPosY[SnakeHeadID];

    switch (movingDirection) {
      case SNAKE_RIGHT:
        SnakeItemPosX[SnakeBackID] += 1;
        //damit snake nicht am seitlichen x rand aufhört
        if (SnakeItemPosX[SnakeBackID] == 41 && !snakeOntop) {
          SnakeItemPosX[SnakeBackID] = 1;
        }
        //verhalten der schlange wenn sie von der oberen matrix auf die seite wechselt
        else if (SnakeItemPosX[SnakeBackID] == 51 && snakeOntop) {
          SnakeItemPosX[SnakeBackID] = 31 - SnakeItemPosY[SnakeBackID];
          SnakeItemPosY[SnakeBackID] = 10;
          snakeDirection = SNAKE_DOWN;
          snakeOntop = false;
        }
        break;

      case SNAKE_LEFT:
        SnakeItemPosX[SnakeBackID] -= 1;
        //damit snake nicht am seitlichen x rand aufhört
        if (SnakeItemPosX[SnakeBackID] < 1 && !snakeOntop) {
          SnakeItemPosX[SnakeBackID] = 40;
        }
        //verhalten der schlange wenn sie von der oberen matrix auf die seite wechselt
        else if (SnakeItemPosX[SnakeBackID] == 40 && snakeOntop) {
          SnakeItemPosX[SnakeBackID] = SnakeItemPosY[SnakeBackID];
          SnakeItemPosY[SnakeBackID] = 10;
          snakeDirection = SNAKE_DOWN;
          snakeOntop = false;
        }
        break;

      case SNAKE_DOWN:
        SnakeItemPosY[SnakeBackID] -= 1;

        //verhalten der schlange wenn sie von der oberen matrix auf die seite wechselt
        if (SnakeItemPosY[SnakeBackID] == 0 && SnakeItemPosX[SnakeBackID] > 40 && snakeOntop) {
          SnakeItemPosY[SnakeBackID] = 10;
          SnakeItemPosX[SnakeBackID] = 81 - SnakeItemPosX[SnakeBackID];
          snakeDirection = SNAKE_DOWN;
          snakeOntop = false;
        }
        break;

      case SNAKE_UP:
        SnakeItemPosY[SnakeBackID] += 1;

        //verhalten der schlange wenn sie von der oberen matrix auf die seite wechselt
        if (SnakeItemPosY[SnakeBackID] == 11 && SnakeItemPosX[SnakeBackID] > 40 && snakeOntop) {
          SnakeItemPosY[SnakeBackID] = 10;
          SnakeItemPosX[SnakeBackID] = SnakeItemPosX[SnakeBackID] - 30;
          snakeDirection = SNAKE_DOWN;
          snakeOntop = false;
        }

        //verhalten der schlange wenn sie von der seite auf die obere matrix wechselt
        else if (SnakeItemPosY[SnakeBackID] > 10 && !snakeOntop) {
          if (SnakeItemPosX[SnakeBackID] > 0 && SnakeItemPosX[SnakeBackID] <= 10) {
            SnakeItemPosY[SnakeBackID] = SnakeItemPosX[SnakeBackID];
            SnakeItemPosX[SnakeBackID] = 41;
            snakeDirection = SNAKE_RIGHT;
          }
          else if (SnakeItemPosX[SnakeBackID] > 10 && SnakeItemPosX[SnakeBackID] <= 20) {
            SnakeItemPosY[SnakeBackID] = 10;
            SnakeItemPosX[SnakeBackID] = SnakeItemPosX[SnakeBackID] + 30;
            snakeDirection = SNAKE_DOWN;
          }
          else if (SnakeItemPosX[SnakeBackID] > 20 && SnakeItemPosX[SnakeBackID] <= 30) {
            SnakeItemPosY[SnakeBackID] = 31 - SnakeItemPosX[SnakeBackID];
            SnakeItemPosX[SnakeBackID] = 50;
            snakeDirection = SNAKE_LEFT;
          }
          else if (SnakeItemPosX[SnakeBackID] > 30 && SnakeItemPosX[SnakeBackID] <= 40) {
            SnakeItemPosY[SnakeBackID] = 1;
            SnakeItemPosX[SnakeBackID] = 81 - SnakeItemPosX[SnakeBackID];
            snakeDirection = SNAKE_UP;
          }
          snakeOntop = true;
        }
        break;
    }
    SnakeHeadID = SnakeBackID;
  }
  else {
    //die schlange isst einen apfel
    for (i = SnakeItems; i >= SnakeHeadID; i--) {
      SnakeItemPosX[i + 1] = SnakeItemPosX[i];
      SnakeItemPosY[i + 1] = SnakeItemPosY[i];
    }

    SnakeItemPosX[SnakeHeadID] = SnakeItemPosX[SnakeHeadID + 1];
    SnakeItemPosY[SnakeHeadID] = SnakeItemPosY[SnakeHeadID + 1];

    switch (movingDirection) {
      case SNAKE_RIGHT:
        SnakeItemPosX[SnakeHeadID] += 1;
        //damit snake nicht am seitlichen x rand aufhört
        if (SnakeItemPosX[SnakeHeadID] == 41 && !snakeOntop) {
          SnakeItemPosX[SnakeHeadID] = 1;
        }
        //verhalten der schlange wenn sie von der oberen matrix auf die seite wechselt
        else if (SnakeItemPosX[SnakeHeadID] == 51 && snakeOntop) {
          SnakeItemPosX[SnakeHeadID] = 31 - SnakeItemPosY[SnakeHeadID];
          SnakeItemPosY[SnakeHeadID] = 10;
          snakeDirection = SNAKE_DOWN;
          snakeOntop = false;
        }
        break;

      case SNAKE_LEFT:
        SnakeItemPosX[SnakeHeadID] -= 1;
        //damit snake nicht am seitlichen x rand aufhört
        if (SnakeItemPosX[SnakeHeadID] < 1 && !snakeOntop) {
          SnakeItemPosX[SnakeHeadID] = 40;
        }
        //verhalten der schlange wenn sie von der oberen matrix auf die seite wechselt
        else if (SnakeItemPosX[SnakeHeadID] == 40 && snakeOntop) {
          SnakeItemPosX[SnakeHeadID] = SnakeItemPosY[SnakeHeadID];
          SnakeItemPosY[SnakeHeadID] = 10;
          snakeDirection = SNAKE_DOWN;
          snakeOntop = false;
        }
        break;

      case SNAKE_DOWN:
        SnakeItemPosY[SnakeHeadID] -= 1;

        //verhalten der schlange wenn sie von der oberen matrix auf die seite wechselt
        if (SnakeItemPosY[SnakeHeadID] == 0 && SnakeItemPosX[SnakeHeadID] > 40 && snakeOntop) {
          SnakeItemPosY[SnakeHeadID] = 10;
          SnakeItemPosX[SnakeHeadID] = 81 - SnakeItemPosX[SnakeHeadID];
          snakeDirection = SNAKE_DOWN;
          snakeOntop = false;
        }
        break;

      case SNAKE_UP:
        SnakeItemPosY[SnakeHeadID] += 1;

        //verhalten der schlange wenn sie von der oberen matrix auf die seite wechselt
        if (SnakeItemPosY[SnakeHeadID] == 11 && SnakeItemPosX[SnakeHeadID] > 40 && snakeOntop) {
          SnakeItemPosY[SnakeHeadID] = 10;
          SnakeItemPosX[SnakeHeadID] = SnakeItemPosX[SnakeHeadID] - 30;
          snakeDirection = SNAKE_DOWN;
          snakeOntop = false;
        }

        //verhalten der schlange wenn sie von der seite auf die obere matrix wechselt
        else if (SnakeItemPosY[SnakeHeadID] > 10 && !snakeOntop) {
          if (SnakeItemPosX[SnakeHeadID] > 0 && SnakeItemPosX[SnakeHeadID] <= 10) {
            SnakeItemPosY[SnakeHeadID] = SnakeItemPosX[SnakeHeadID];
            SnakeItemPosX[SnakeHeadID] = 41;
            snakeDirection = SNAKE_RIGHT;
          }
          else if (SnakeItemPosX[SnakeHeadID] > 10 && SnakeItemPosX[SnakeHeadID] <= 20) {
            SnakeItemPosY[SnakeHeadID] = 10;
            SnakeItemPosX[SnakeHeadID] = SnakeItemPosX[SnakeHeadID] + 30;
            snakeDirection = SNAKE_DOWN;
          }
          else if (SnakeItemPosX[SnakeHeadID] > 20 && SnakeItemPosX[SnakeHeadID] <= 30) {
            SnakeItemPosY[SnakeHeadID] = 31 - SnakeItemPosX[SnakeHeadID];
            SnakeItemPosX[SnakeHeadID] = 50;
            snakeDirection = SNAKE_LEFT;
          }
          else if (SnakeItemPosX[SnakeBackID] > 30 && SnakeItemPosX[SnakeHeadID] <= 40) {
            SnakeItemPosY[SnakeHeadID] = 1;
            SnakeItemPosX[SnakeHeadID] = 81 - SnakeItemPosX[SnakeHeadID];
            snakeDirection = SNAKE_UP;
          }
          snakeOntop = true;
        }
        break;
    }

    SnakeItems++;
    AddSnakeItem--;
  }

  //Abbruchbedingungen
  //Snake darf keine wand berühren
  //if (SnakeItemPosX[SnakeHeadID] > 0 && SnakeItemPosX[SnakeHeadID] <= BaneGridXmax && SnakeItemPosY[SnakeHeadID] > 0 && SnakeItemPosY[SnakeHeadID] <= BaneGridYmax) {
  //Snake darf nur oben und unten nicht berühren
  if (SnakeItemPosY[SnakeHeadID] > 0) {
    //Snake kommt nicht an den Rand
    if (Playfield[SnakeItemPosX[SnakeHeadID]][SnakeItemPosY[SnakeHeadID]] != SNAKE) {
      if (Playfield[SnakeItemPosX[SnakeHeadID]][SnakeItemPosY[SnakeHeadID]] == APPLE) {
        //Snake isst einen Apple

        playEffect(0);

        if (snakespeed > snakespeedmax + 10) snakespeed = snakespeed - 10;

        SnakeScore++;
        //add one
        AddSnakeItem += SnakeItemsToAddAtApple;
        AppleCount--;
      }

      if (AppleCount == 0) {
        //falls kein Apple mehr auf dem Spielfeld
        placeRandomApple();
      }
      render();
    }
    else {
      //Snake trifft sich selber
      GameOver();
    }
  }
  else {
    //Snake kommt an den Rand
    GameOver();
  }
}//end moveSnake()

void placeRandomApple() {
  byte x, y;
  x = random(1, BaneGridXmax);
  y = random(1, BaneGridYmax);

  //Suche eine Stelle an der noch keine led leuchtet
  while (Playfield[x][y] != BLANK) {
    x = random(1, BaneGridXmax);
    y = random(1, BaneGridYmax);
  }
  placeApple(x, y);
}//placeRandomApple()

void placeApple(byte x, byte y) {
  if (x > 0 && y > 0 && x <= BaneGridXmax && y <= BaneGridYmax) {
    //befinden sich x,y im Spielfeld
    Playfield[x][y] = APPLE;
    ApplePosX = x;
    ApplePosY = y;

    AppleCount++;
  }
}//end placeApple()

void removeApple() {
  Playfield[ApplePosX][ApplePosY] = BLANK;
  AppleCount = 0;
}//end removeapple()

void GameOver() {
  //if (SnakeItemPosX[SnakeHeadID] == 0) SnakeItemPosX[SnakeHeadID]++;
  //else if (SnakeItemPosX[SnakeHeadID] > BaneGridXmax) SnakeItemPosX[SnakeHeadID]--;

  if (SnakeItemPosY[SnakeHeadID] == 0) SnakeItemPosY[SnakeHeadID]++;
  else if (SnakeItemPosY[SnakeHeadID] > BaneGridYmax) SnakeItemPosY[SnakeHeadID]--;

  if (SnakeHeadID < SnakeItems)
    setXYPixel(SnakeItemPosX[SnakeHeadID + 1], SnakeItemPosY[SnakeHeadID + 1], CRGB::Green);
  else
    setXYPixel(SnakeItemPosX[1], SnakeItemPosY[1], CRGB::Green);

  setXYPixel(SnakeItemPosX[SnakeHeadID], SnakeItemPosY[SnakeHeadID], CRGB::Aqua);

  FastLED.show();

  snakerun = false;

  FastLED.clear();
  StringToTextBuffer("GAME OVER", 1);

  playEffect(2);

  matrix_top.DrawFilledRectangle(0, 0, 9, 9, CRGB::Red);
  while (scrollingtext.UpdateText() != -1) {
    FastLED.show();
    delay(50);
  }

  //  if (CheckHighscore(SnakeScore)) {
  //    //new Highscore
  //    InitText(Txt_NewHighscore);
  //    while (scrollingtext.UpdateText() != -1) {
  //      FastLED.show();
  //      delay(50);
  //    }
  //  }

  resetDos();
  doNoise = true;
}//end GameOver()

boolean CheckHighscore(long score) {
  boolean newhighscore = false;
  SnakeHighscore = EEPROMReadlong(ScoreEEPROMaddress);
  if (score > SnakeHighscore) {
    EEPROMWritelong(score, ScoreEEPROMaddress);
    newhighscore = true;
  }
  return newhighscore;
}//end CheckScore()

void setSnakeDirection(int dir) {
  if (dir == 1) {
    if (snakeOntop) {
      if (snakeDirection == SNAKE_RIGHT) snakeDirection = SNAKE_UP;
      else if (snakeDirection == SNAKE_DOWN) snakeDirection = SNAKE_RIGHT;
      else if (snakeDirection == SNAKE_LEFT) snakeDirection = SNAKE_DOWN;
      else if (snakeDirection == SNAKE_UP) snakeDirection = SNAKE_LEFT;
    } else {
      if (snakeDirection == SNAKE_RIGHT) snakeDirection = SNAKE_DOWN;
      else if (snakeDirection == SNAKE_DOWN) snakeDirection = SNAKE_LEFT;
      else if (snakeDirection == SNAKE_LEFT) snakeDirection = SNAKE_UP;
      else if (snakeDirection == SNAKE_UP) snakeDirection = SNAKE_RIGHT;
    }
  }
  else if (dir == -1) {
    if (snakeOntop) {
      if (snakeDirection == SNAKE_RIGHT) snakeDirection = SNAKE_DOWN;
      else if (snakeDirection == SNAKE_DOWN) snakeDirection = SNAKE_LEFT;
      else if (snakeDirection == SNAKE_LEFT) snakeDirection = SNAKE_UP;
      else if (snakeDirection == SNAKE_UP) snakeDirection = SNAKE_RIGHT;
    }
    else {
      if (snakeDirection == SNAKE_RIGHT) snakeDirection = SNAKE_UP;
      else if (snakeDirection == SNAKE_DOWN) snakeDirection = SNAKE_RIGHT;
      else if (snakeDirection == SNAKE_LEFT) snakeDirection = SNAKE_DOWN;
      else if (snakeDirection == SNAKE_UP) snakeDirection = SNAKE_LEFT;
    }
  }
  else {
    snakeDirection = -1;
  }
}//end setSnakeDirection()

【Arduino 动手做】RGB-Brick:100颗WS2812 LED 构建了一个 10x10 LED 魔方
项目链接:https://www.instructables.com/500-LED-Pixel-RGB-Brick/
项目作者:moekoe

项目视频 :https://www.youtube.com/watch?v=qAinUa67InU
项目代码:
https://content.instructables.com/F1D/HMOE/IUYD90XN/F1DHMOEIUYD90XN.ino
https://content.instructables.com/FYZ/SU5P/IUYD91O6/FYZSU5PIUYD91O6.ino
https://content.instructables.com/FDB/XBUT/ITKJCF4O/FDBXBUTITKJCF4O.json

 

11.jpg
20.jpg
00187---0.gif

评论

user-avatar
icon 他的勋章
    展开更多