Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 4 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Hand tools and fabrication machines | ||||||
| ||||||
| ||||||
| ||||||
| ||||||
| ||||||
| ||||||
|
To begin with, I would like to thank my parents and my grandfathers who taught me from an early age the worth and the power of creative work.
WARNING : This is a showcase, not a tutorial. The published code is for illustration and is not running without other files (containing application source codes). Since the games are not fully debugged, I do not want to share crappy code. Sorry about that :(
[04/01/2019 UPDATE] The code is now fully uploaded (Bluetooth and buttons mangement, animations, games fully operational) on this showcase but still verry buggy (RAM overflow)
0. PresentationIn this showcase, I'll explain you how I made a cheap interactive table that uses Bluetooth, physical controls and a LED matrix from a simple 7€ IKEA table. This table is able to display an audio spectrum visualizer, some games and animations.
You can see some of these features in the video below :
1. Preparing the project - Java emulationAfter having the idea of a project, the first thing to be done is to define an exhaustive to-do list, a bill of materials and to have a strong idea of what your code will look like.
To cope with this challenge, I designed an emulator for my LED Matrix on Java. The purpose of this step was to set up the main functions and algorithms that I would need to make my device work properly. Doing this also allowed me to know more specifically what hardware (inputs especially) I would need to complete my project.
This program displays a 12x12 color grid and refreshes this grid with a method similar to the "FastLED.show();" function used by the Arduino FastLED library to control the matrix. The program displays the menu and is able to launch several modes : displaying images/animations/text, run Conway's game of life, Tetris, Snake, Pong (for 2 players), the Simon game, etc...
The first issue that I had was to define some static images in the program. The specifications of the Arduino didn't allow me to use .jpg nor .png images, I had to deal with 2D-arrays of 24bits pixels (defined in hexadecimal as 0xRRGGBB). To make things easier, I wrote another Java program that translates a 12x12 bitmap image into the desired 2D-Array.
Once the emulator was in accordance with my expectations, I bought all the necesary components for the project...
2. How to read an audio signal ?One of the main feature of my table will be to display live an audio spectrum on the screen while I listen to music. In order to achieve that, I used an operational amplifier (the TL072) to center the voltage around 2.5V and to amplify it. The Arduino is now able to read and to analyze the provided audio signal. (thanks to a Fast Fourier Transformation algorithm). This circuit was soldered on a prototype board.
The 150 LEDs strip that I bought was 5 meters (or 16.4ft) long. That means that my 12x12 screen would be at least (500/150*12)=40cm width and long. In order to have more flexibility in the future, I ordered a 405x405mm white acrylic glass and made a 410x410mm hole in the table with a drill and a saw.
The 7€ Ikea Lack table is so cheap that it is empty on the inside but this is beneficent for our purpose. (By the way, having a vacuum cleaner at your side prevents you from breathing wood dust)
Once I was done with the main hole, I drilled 4 round 40mm holes on the sides of the table to host 4 control buttons and a small hole on the bottom of the table to host the female powerplug. Afterwards, I made a square hole where the main control interface will fit. The buttons on this interface are very small and close to each other, drilling some holes would have led to disappointing results.
In order to solve this problem, I've designed my interface on Fusion360 and 3D printed it. Next, I applied a primer coat on it and was quite satisfied by this finish.
Then, it was time for some wiring. I've soldered and glued everything in place as it is shown in the schematic below.
I think this is the part I'm the less proud of. I strongly advice you to use a very soft foam-board or even a laser cutter to make a decent grid. I cut the LED strip every 12 leds to make 12 small strips and glued them on a 410x410mm foam-board (with the wiring done). I then glued a foam-board grid made with a box cutter. Finally, I glued the acrylic glass on the top of the grid and powered the LED matrix for a test. The problem with the box cutter is that the grid isn't very flat on the top and that the pixels aren't perfectly aligned.
Once installed and wired in the table, the matrix was ready to execute the code that we provided to the Arduino through the USB port.
5. The Android controllerI used the MIT App Inventor software to make a very simple Android application to control my table through Bluetooth.
The work isn't finished yet. I need to debug some features, to improve some and even to program some others !
I also want to cut and install some L-profiled aluminium bar in the gap between the screen and the table to make it look nicer and cleaner but I need a hacksaw for that...
This is still work in progress ;)
SCHEMATICS COMING SOON
Arduino main code
ArduinoI'll upload them once the code is finished and bugless.
/*
*
* Code by Antoine ROCHEBOIS : CC-BY-NC
*
*
* This file is the main code that run on the Arduino, it uses several
* home-made methods & functions to run games and other features, I'll * upload them once the code is finished and bugless.
*
*
*
* */
#include "FastLED.h"
#include <avr/pgmspace.h>
#include <SoftwareSerial.h>
#include "imgMario.h" //Import 2D array of 32bits int provinding the RGB
#include "imgMenu.h" //code for each image of the Menu and Images function
#define M_PIN 2 //Pin for the menuInterrupt
#define R1_PIN 8 //
#define L1_PIN 9 // Digital inputs used by arcade buttons (side)
#define R2_PIN 6 //
#define L2_PIN 7 //
#define DATA_PIN 5 //Data PIN for the led matrix
#define COLOR_ORDER GRB // if colors are mismatched; change this
#define NUM_LEDS 144 // 12*12=144 leds in the strip
#define LED_TYPE WS2812B
// this creates an LED array to hold the values for each led in your strip
CRGB leds[NUM_LEDS];
const PROGMEM byte ledsAdress[12][12] = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
{23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12},
{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35},
{47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36},
{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59},
{71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60},
{72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83},
{95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84},
{96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107},
{119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108},
{120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131},
{143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, 132}
}; //Adress of each led in the matrix (Progmem means stored in FLASH instead //of SRAM)
SoftwareSerial BT(10, 11); //Emulates an Serial BT terminal
boolean grid[12][12]; //(used in a game, ignore)
char message; //char read on the BT terminal
char curControl; //current control ID
int menuPointer = 0; //Position in the menu
volatile bool menuValidation = 0; //Variable set to true when the menu switch
//is pushed
unsigned long curTime;
unsigned long prevUpdateTime = 0; //Variables for timed events
unsigned long prevButtonTime = 0;
//Function executed when the user push the menuButton (set menuValidation to
//true + debounce input)
void menuInterrupt() {
static unsigned long last_interrupt_time = 0;
unsigned long interrupt_time = millis();
if (interrupt_time - last_interrupt_time > 200) {
menuValidation = true;
}
last_interrupt_time = interrupt_time;
}
//Displays static images stocked in FLASH, if roll == 1, draws it whith a fancy red line
void setImg(const unsigned long frame[12][12], bool roll) {
if (!roll) {
for (byte i = 0; i < 12; i++) {
for (byte j = 0; j < 12; j++) {
leds[pgm_read_byte(&ledsAdress[i][j])] = pgm_read_dword(&frame[i][j]);
}
}
FastLED.show();
} else {
for (uint8_t i = 11; i > 0; i--) {
for (byte j = 0; j < 12; j++) {
if (i > 0) {
leds[pgm_read_byte(&ledsAdress[i][j])] = 0xe22c79;
} else {
leds[pgm_read_byte(&ledsAdress[i][j])] = pgm_read_dword(&frame[i][j]);
}
leds[pgm_read_byte(&ledsAdress[(i + 13) % 12][j])] = pgm_read_dword(&frame[(i + 13) % 12][j]);
}
FastLED.show();
delay(70);
}
}
}
//Erase the screen with a red sweeping line
void resetImg() {
for (byte i = 0; i < 12; i++) {
for (byte j = 0; j < 12; j++) {
leds[pgm_read_byte(&ledsAdress[i][j])] = 0xe22c79;
if (i > 0) {
leds[pgm_read_byte(&ledsAdress[(i + 11) % 12][j])] = 0x000000;
}
}
FastLED.show();
delay(70);
}
}
//Erase the screen by fading it
void fadeImg() {
for (uint8_t i = 128; i > 0; i = i - 4) {
FastLED.setBrightness(i);
FastLED.show();
delay(24);
}
delay(250);
FastLED.setBrightness(128);
}
//Instant erase of the screen
void clearImg() {
for (byte dim1 = 0; dim1 < 12; dim1++) {
for (byte dim2 = 0; dim2 < 12; dim2++) {
leds[pgm_read_byte(&ledsAdress[dim1][dim2])] = 0x000000;
}
}
}
//Read inputs (BT + digital IO)
void readPointer() {
//Read BT (hc-05 module)
while (BT.available() > 0) {
message = BT.read();
}
//Changes control variables
if (message == 'q' ) {
--menuPointer;
curControl = 'q';
} else if (message == 'd') {
++menuPointer;
curControl = 'd';
} else if (message == 'z') {
curControl = 'z';
} else if (message == 's') {
curControl = 's';
} else if (message == 'a') {
curControl = 'a';
} else if (message == 'y') {
menuValidation = true;
}
message = 'n'; //Reset message
//Read digital IO pins
if (!digitalRead(L1_PIN)) {
if ( (millis() - prevButtonTime) > 150) {
curControl = 'q';
--menuPointer;
prevButtonTime = millis();
}
} else if (!digitalRead(R1_PIN)) {
if ( (millis() - prevButtonTime) > 150) {
curControl = 'd';
++menuPointer;
prevButtonTime = millis();
}
} else if (!digitalRead(L2_PIN)) {
if ( (millis() - prevButtonTime) > 150) {
curControl = 'z';
--menuPointer;
prevButtonTime = millis();
}
} else if (!digitalRead(l2_PIN)) {
if ( (millis() - prevButtonTime) > 150) {
curControl = 's';
++menuPointer;
prevButtonTime = millis();
}
}
}
#include "gameOfLife.h" //Include features and games methods and functions
#include "snake.h"
#include "pong.h"
#include "tetris.h"
#include "bottle.h"
#include "decoration.h"
#include "font.h"
/*
*
* RUN AT STARTUP
*
*/
void setup() {
//Setup the BT connection
BT.begin(9600);
//Setup the IO pin (Inputs with a built-in pull-up resistor or interupt)
pinMode(R1_PIN, INPUT_PULLUP);
pinMode(L1_PIN, INPUT_PULLUP);
pinMode(R2_PIN, INPUT_PULLUP);
pinMode(L2_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(M_PIN), menuInterrupt, FALLING);
//Setup the Led strip
FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);
//Intro animation (display a beer and load the menu)
FastLED.setBrightness(0);
setImg(beer, 0);
delay(250);
for (uint8_t i = 0; i <= 128; i = i + 4) {
FastLED.setBrightness(i);
FastLED.show();
delay(42);
}
delay(2400);
fadeImg();
clearImg();
FastLED.show();
delay(250);
setImg(menuTetris, 1);
}
/*
*
* RUN PERPETUALY
*
*/
void loop() {
//Menu Interface and games/features launcher
switch ((menuPointer + 12000) % 12) {
case 0:
setImg(menuTetris, 0);
while ((menuPointer + 12000) % 12 == 0 && !menuValidation) {
readPointer();
}
if (menuValidation) {
menuValidation = false;
while (!menuValidation) {
launchTetris(); //Tetris game
}
menuValidation = false;
menuPointer = 0;
resetImg();
setImg(menuTetris, 1);
}
break;
case 1:
setImg(menuSimon, 0);
while ((menuPointer + 12000) % 12 == 1 && !menuValidation) {
readPointer();
}
if (menuValidation) {
menuValidation = false; //Displays a cross : feature not implemented yet
setImg(menuError, 0);
delay(1250);
menuPointer = 1;
resetImg();
setImg(menuSimon, 1);
}
break;
case 2:
setImg(menuPong, 0);
while ((menuPointer + 12000) % 12 == 2 && !menuValidation) {
readPointer();
}
if (menuValidation) {
menuValidation = false;
while (!menuValidation) {
launchPong(); //Pong game for 2 players
}
menuValidation = false;
menuPointer = 2;
resetImg();
setImg(menuPong, 1);
}
break;
case 3:
setImg(menuSnake, 0);
while ((menuPointer + 12000) % 12 == 3 && !menuValidation) {
readPointer();
}
if (menuValidation) {
menuValidation = false;
while (!menuValidation) {
launchSnake(); //Snake game
}
menuValidation = false;
menuPointer = 3;
resetImg();
setImg(menuSnake, 1);
}
break;
case 4:
setImg(menuBottle, 0);
while ((menuPointer + 12000) % 12 == 4 && !menuValidation) {
readPointer();
}
if (menuValidation) {
menuValidation = false;
while (!menuValidation) {
bottle(); //A wheel that stops on a random location on the screen
}
menuValidation = false;
menuPointer = 4;
resetImg();
setImg(menuBottle, 1);
}
break;
case 5:
setImg(menuGameOfLife, 0);
while ((menuPointer + 12000) % 12 == 5 && !menuValidation) {
readPointer();
}
if (menuValidation) {
menuValidation = false;
while (!menuValidation) {
gameOfLife(); //Run conway's game of life
}
menuValidation = false;
menuPointer = 5;
resetImg();
setImg(menuGameOfLife, 1);
}
break;
case 6:
setImg(menuParty, 0);
while ((menuPointer + 12000) % 12 == 6 && !menuValidation) {
readPointer();
}
if (menuValidation) {
menuValidation = false;
while (!menuValidation) {
launchManualAnimations(); //Visual animation (choice)
}
menuValidation = false;
menuPointer = 6;
resetImg();
setImg(menuParty, 1);
}
break;
case 7:
setImg(menuParty, 0);
while ((menuPointer + 12000) % 12 == 7 && !menuValidation) {
readPointer();
}
if (menuValidation) {
menuValidation = false;
while (!menuValidation) {
launchRandomAnimations(); //Launch random visual animation
}
menuValidation = false;
menuPointer = 7;
resetImg();
setImg(menuParty, 1);
}
break;
case 8:
setImg(menuImage, 0);
while ((menuPointer + 12000) % 12 == 8 && !menuValidation) {
readPointer();
}
if (menuValidation) {
menuValidation = false;
while (!menuValidation) {
launchManualImage(); //Images manual selection
}
menuValidation = false;
menuPointer = 8;
resetImg();
setImg(menuImage, 1);
}
break;
case 9:
setImg(menuImage, 0);
while ((menuPointer + 12000) % 12 == 9 && !menuValidation) {
readPointer();
}
if (menuValidation) {
menuValidation = false;
while (!menuValidation) {
launchRandomImage(); //Images random selection
}
menuValidation = false;
menuPointer = 9;
resetImg();
setImg(menuImage, 1);
}
break;
case 10:
setImg(menuText, 0);
while ((menuPointer + 12000) % 12 == 10 && !menuValidation) {
readPointer();
}
if (menuValidation) {
menuValidation = false;
while (!menuValidation) {
scrollText("Hi!",3, 0xff0000); //Scrolls a defined text on the screen TODO: BT defined text & color
}
menuValidation = false;
menuPointer = 10;
resetImg();
setImg(menuText, 1);
}
break;
case 11:
setImg(menuSpectrum, 0);
while ((menuPointer + 12000) % 12 == 11 && !menuValidation) {
readPointer();
}
if (menuValidation) { //Spectrum Analyzer code is too heavy for the FLASH/SRAM, needs a specific upload to run. Displays an error message
menuValidation = false;
setImg(menuError, 0);
delay(1250);
scrollText("Connect USB",11, 0xff0000);
menuPointer = 11;
resetImg();
setImg(menuSpectrum, 1);
}
break;
}
}
void gameOfLife(void) {
//Génération d'une grille de départ aléatoire
for (byte i = 0; i < 12; i++) {
for (byte j = 0; j < 12; j++) {
byte r = random(0, 100);
if (r < 35) {
grid[i][j] = true;
leds[pgm_read_byte(&ledsAdress[i][j])] = 0x2CB697;
} else {
grid[i][j] = false;
leds[pgm_read_byte(&ledsAdress[i][j])] = 0x11463A;
}
}
}
FastLED.show();
//Tant que la grille n'est pas vide
byte nbCases;
byte iterations = 0;
do {
readPointer();
if (menuValidation) {
break;
}
++iterations;
if (iterations > 250) {
break;
}
//Délai de 150ms
delay(120);
//Initialisation et comptage du nombre de voisins de chaque case
nbCases = 0;
byte voisins[12][12];
for (byte i = 0; i < 12; i++) {
for (byte j = 0; j < 12; j++) {
voisins[i][j] = 0;
}
}
for (byte i = 0; i < 12; i++) {
for (byte j = 0; j < 12; j++) {
for (int k = -1; k < 2; k++) {
for (int m = -1; m < 2; m++) {
if ((grid[(i + k + 12) % 12][(j + m + 12) % 12] == true) && !(k == 0 && m == 0)) {
voisins[i][j]++;
}
}
}
}
}
//Détermination des cellules vivantes et mortes et mise à jour de l'affichage
for (byte i = 0; i < 12; i++) {
for (byte j = 0; j < 12; j++) {
if (grid[i][j]) {
nbCases++;
//Cellule vivante reste vivante avec 2 ou 3 voisins
if (voisins[i][j] == 2 || voisins[i][j] == 3) {
grid[i][j] = true;
leds[pgm_read_byte(&ledsAdress[i][j])] = 0x2CB697;
} else {
grid[i][j] = false;
leds[pgm_read_byte(&ledsAdress[i][j])] = 0x11463A;
}
} else {
if (voisins[i][j] == 3) {
//Cellule morte devient vivante si elle a 3 voisins
grid[i][j] = true;
leds[pgm_read_byte(&ledsAdress[i][j])] = 0x2CB697;
}
}
}
}
FastLED.show();
if (nbCases <= 4) {
for (byte i = 0; i < 12; i++) {
for (byte j = 0; j < 12; j++) {
if (i < 12 - 1) {
leds[pgm_read_byte(&ledsAdress[i][j])] = 0xe22c79;
} else {
leds[pgm_read_byte(&ledsAdress[i][j])] = 0x11463A;
}
leds[pgm_read_byte(&ledsAdress[(i + 11) % 12][j])] = 0x11463A;
}
FastLED.show();
delay(100);
}
}
} while (nbCases > 4);
}
const unsigned long PROGMEM img[12][12] = {{0x020EF4, 0x022FF4, 0x0159F3, 0x018AF5, 0x01BFF9, 0x01F5FB, 0x01FFD6, 0x01FDA5, 0x01FC7A, 0x01FB54, 0x01FA38, 0x02F91F},
{0x0100F3, 0x010EF4, 0x0237F3, 0x0170F4, 0x01B1F9, 0x01F2FA, 0x01FFCB, 0x01FC8E, 0x01FB5D, 0x01FA37, 0x01F918, 0x00F905},
{0x0001F2, 0x0000F2, 0x010FF3, 0x0247F4, 0x019BF7, 0x01EFF8, 0x01FFBA, 0x01FB6E, 0x01FA35, 0x00F812, 0x00F802, 0x01F900},
{0x0201F2, 0x0001F2, 0x0000F3, 0x0010F3, 0x016CF6, 0x01E9F5, 0x00FF97, 0x00FA35, 0x00F908, 0x02F900, 0x0EFA00, 0x1FFB01},
{0x2C00F3, 0x1D01F3, 0x0F01F2, 0x0100F2, 0x001BF9, 0x00CEE4, 0x00FF42, 0x08F901, 0x1DFB00, 0x33FC01, 0x41FE00, 0x4BFE00},
{0x6701F3, 0x6501F3, 0x6501F4, 0x6402F5, 0x6700FE, 0x756F82, 0x7CFF00, 0x7BFF02, 0x7AFF02, 0x7BFF02, 0x7AFE02, 0x79FF01},
{0x9F00F3, 0xAC01F3, 0xC301F7, 0xE001F1, 0xF900AE, 0xFF0C15, 0xFFAF00, 0xF3FD04, 0xD8FF03, 0xC0FF03, 0xB2FF04, 0xA9FE04},
{0xD301F5, 0xE600F2, 0xF401DF, 0xF600AF, 0xF4004F, 0xF30001, 0xF95000, 0xFFC002, 0xFFF303, 0xF7FF03, 0xE6FF02, 0xD3FF03},
{0xF301E9, 0xF501D0, 0xF301A9, 0xF30074, 0xF20026, 0xF10000, 0xF52A01, 0xFA8202, 0xFFC003, 0xFFEA04, 0xFFFD03, 0xF7FF03},
{0xF400C5, 0xF301A8, 0xF20182, 0xF2014F, 0xF20113, 0xF20000, 0xF41801, 0xF75C02, 0xFD9502, 0xFEBF02, 0xFFE103, 0xFFF803},
{0xF301A8, 0xF30089, 0xF20065, 0xF10137, 0xF20009, 0xF20000, 0xF30D01, 0xF64301, 0xF97601, 0xFDA001, 0xFEC002, 0xFFDB03},
{0xF30090, 0xF20172, 0xF2014E, 0xF30126, 0xF20003, 0xF20000, 0xF30801, 0xF43201, 0xF75F02, 0xFC8602, 0xFEA601, 0xFEC102}
};
void bottleAnimation(byte t, byte stopb) {
byte iterations = 0;
while (!menuValidation) {
for (byte k = 0; k < 4; k++) {
switch (k) {
case 0:
for (byte i = 2; i < 12; i++) {
iterations++;
if (iterations == stopb) {
goto mainLoopBottle;
}
clearImg();
leds[pgm_read_byte(&ledsAdress[2][i - 2])] = pgm_read_dword(&img[1][i - 2]);
leds[pgm_read_byte(&ledsAdress[2][i - 1])] = pgm_read_dword(&img[2][i - 1]);
leds[pgm_read_byte(&ledsAdress[2][i])] = pgm_read_dword(&img[2][i]);
leds[pgm_read_byte(&ledsAdress[1][i - 2])] = pgm_read_dword(&img[1][i - 2]);
leds[pgm_read_byte(&ledsAdress[1][i - 1])] = pgm_read_dword(&img[1][i - 1]);
leds[pgm_read_byte(&ledsAdress[1][i])] = pgm_read_dword(&img[1][i]);
leds[pgm_read_byte(&ledsAdress[0][i - 2])] = pgm_read_dword(&img[1][i - 2]);
leds[pgm_read_byte(&ledsAdress[0][i])] = pgm_read_dword(&img[0][i]);
leds[pgm_read_byte(&ledsAdress[0][i - 1])] = pgm_read_dword(&img[1][i - 1]);
FastLED.show();
delay(t);
}
break;
case 1:
for (byte i = 2; i < 12; i++) {
iterations++;
if (iterations == stopb) {
goto mainLoopBottle;
}
clearImg();
leds[pgm_read_byte(&ledsAdress[i - 2][11])] = pgm_read_dword(&img[i - 2][11]);
leds[pgm_read_byte(&ledsAdress[i - 1][11])] = pgm_read_dword(&img[i - 1][11]);
leds[pgm_read_byte(&ledsAdress[i][11])] = pgm_read_dword(&img[i][11]);
leds[pgm_read_byte(&ledsAdress[i - 2][10])] = pgm_read_dword(&img[i - 2][10]);
leds[pgm_read_byte(&ledsAdress[i - 1][10])] = pgm_read_dword(&img[i - 1][10]);
leds[pgm_read_byte(&ledsAdress[i][10])] = pgm_read_dword(&img[i][10]);
leds[pgm_read_byte(&ledsAdress[i - 2][9])] = pgm_read_dword(&img[i - 2][9]);
leds[pgm_read_byte(&ledsAdress[i - 1][9])] = pgm_read_dword(&img[i - 1][9]);
leds[pgm_read_byte(&ledsAdress[i][9])] = pgm_read_dword(&img[i][9]);
FastLED.show();
delay(t);
}
break;
case 2:
for (byte i = 12 - 1; i > 1; i--) {
iterations++;
if (iterations == stopb) {
goto mainLoopBottle;
}
clearImg();
leds[pgm_read_byte(&ledsAdress[11][i - 2])] = pgm_read_dword(&img[11][i - 2]);
leds[pgm_read_byte(&ledsAdress[11][i - 1])] = pgm_read_dword(&img[11][i - 1]);
leds[pgm_read_byte(&ledsAdress[11][i])] = pgm_read_dword(&img[11][i]);
leds[pgm_read_byte(&ledsAdress[10][i - 2])] = pgm_read_dword(&img[10][i - 2]);
leds[pgm_read_byte(&ledsAdress[10][i - 1])] = pgm_read_dword(&img[10][i - 1]);
leds[pgm_read_byte(&ledsAdress[10][i])] = pgm_read_dword(&img[10][i]);
leds[pgm_read_byte(&ledsAdress[9][i - 2])] = pgm_read_dword(&img[9][i - 2]);
leds[pgm_read_byte(&ledsAdress[9][i])] = pgm_read_dword(&img[9][i]);
leds[pgm_read_byte(&ledsAdress[9][i - 1])] = pgm_read_dword(&img[9][i - 1]);
FastLED.show();
delay(t);
}
break;
case 3:
for (byte i = 12 - 1; i > 1; i--) {
iterations++;
if (iterations == stopb) {
goto mainLoopBottle;
}
clearImg();
leds[pgm_read_byte(&ledsAdress[i - 2][2])] = pgm_read_dword(&img[i - 2][2]);
leds[pgm_read_byte(&ledsAdress[i - 1][2])] = pgm_read_dword(&img[i - 1][2]);
leds[pgm_read_byte(&ledsAdress[i][2])] = pgm_read_dword(&img[i][2]);
leds[pgm_read_byte(&ledsAdress[i - 2][1])] = pgm_read_dword(&img[i - 2][1]);
leds[pgm_read_byte(&ledsAdress[i - 1][1])] = pgm_read_dword(&img[i - 1][1]);
leds[pgm_read_byte(&ledsAdress[i][1])] = pgm_read_dword(&img[i][1]);
leds[pgm_read_byte(&ledsAdress[i - 2][0])] = pgm_read_dword(&img[i - 2][0]);
leds[pgm_read_byte(&ledsAdress[i - 1][0])] = pgm_read_dword(&img[i - 1][0]);
leds[pgm_read_byte(&ledsAdress[i][0])] = pgm_read_dword(&img[i][0]);
FastLED.show();
delay(t);
}
break;
}
}
} mainLoopBottle:
delay(4);
}
void bottle() {
bottleAnimation(60, random(100, 139));
int prevPointer = menuPointer;
while (!menuValidation && menuPointer == prevPointer) {
readPointer();
delay(4);
}
}
/*void fillingScreen() {
int iterations = 0;
while (!menuValidation && iterations < 400) {
byte c[3];
for (byte i = 0; i < 3; i++) {
c[i] = random(0, 256);
}
for (byte i = 0; i < 12; i++) {
for (byte j = 0; j < 12; j++) {
leds[pgm_read_byte(&ledsAdress[i][j])] = (( (c[0])) << 16) + ( (c[1]) << 8) + ( (c[2]));
FastLED.show();
delay(25);
iterations++;
}
}
}
}
void fadingScreen() {
int iterations = 0;
while (!menuValidation && iterations < 400) {
byte c[3];
for (byte i = 0; i < 3; i++) {
c[i] = random(0, 256);
}
for (byte k = 0; k < 128; k = k + 2) {
for (byte i = 0; i < 12; i++) {
for (byte j = 0; j < 12; j++) {
leds[pgm_read_byte(&ledsAdress[i][j])] = (( (c[0])) << 16) + ( (c[1]) << 8) + ( (c[2]));
}
}
iterations++;
FastLED.setBrightness(k);
FastLED.show();
delay(50);
}
for (uint8_t k = 128; k > 0; k = k - 2) {
for (byte i = 0; i < 12; i++) {
for (byte j = 0; j < 12; j++) {
leds[pgm_read_byte(&ledsAdress[i][j])] = (( (c[0])) << 16) + ( (c[1]) << 8) + ( (c[2]));
}
}
iterations++;
FastLED.setBrightness(k);
FastLED.show();
delay(50);
}
}
clearImg();
FastLED.setBrightness(128);
}*/
void crossingSnakes() {
for (byte i = 0; i < 144; i++) {
for (byte j = 0; j < 5; j++) {
leds[(i + j + 0) % 144] = 0xff0000;
leds[(i + j + 12) % 144] = 0xffff00;
leds[(i + j + 23) % 144] = 0x00ff00;
leds[(i + j + 36) % 144] = 0x00ffff;
leds[(i + j + 48) % 144] = 0x0000ff;
leds[(i + j + 59) % 144] = 0xff00ff;
leds[(i + j + 72) % 144] = 0xff0000;
leds[(i + j + 84) % 144] = 0xffff00;
leds[(i + j + 95) % 144] = 0x00ff00;
leds[(i + j + 107) % 144] = 0x00ffff;
leds[(i + j + 120) % 144] = 0x0000ff;
leds[(i + j + 134) % 144] = 0xff00ff;
}
FastLED.show();
delay(85);
clearImg();
if (menuValidation) break;
}
}
/*public void rainingStars(Panel p) {
int iterations = 0;
while (!TableLED.menuValidation && iterations < 400) {
int img[][] = new int[12][12];
int color[][] = new int[12][12];
boolean stars[][] = new boolean[12][12];
for (int i = 0; i < stars.length; i++) {
for (int j = 0; j < stars[i].length; j++) {
if (Math.random() < 0.035) {
stars[i][j] = true;
color[i][j] = (int) (Math.random() * 4);
}
}
}
for (int k = 0; k < 256; k = k + 4) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
iterations++;
for (int i = 0; i < stars.length; i++) {
for (int j = 0; j < stars[i].length; j++) {
if (stars[i][j]) {
switch (color[i][j]) {
case 0:
img[i][j] = (((int) (0.2 * k)) << 16) + ((int) (0.32 * k) << 8) + ((int) (0.7 * k));
break;
case 1:
img[i][j] = (((int) (0.28 * k)) << 16) + ((int) (0.463 * k) << 8) + k;
break;
case 2:
img[i][j] = (k << 16) + ((int) (0.757 * k) << 8) + ((int) (0.146 * k));
break;
default:
img[i][j] = (((int) (0.27 * k)) << 16) + ((int) (0.118 * k) << 8) + ((int) (0.44 * k));
break;
}
}
}
}
p.setFrame(img);
}
}
}*/
/*void decoration() {
byte r = random(0, 6);
readPointer();
switch (r) {
case 0:
crossingSnakes();
break;
case 1:
glitchRainingStars();
break;
case 2:
rainingStars();
break;
case 3:
fadingScreen();
break;
case 4:
bottleAnimation(60, 334);
default:
fillingScreen();
break;
}
}*/
/* uTFT Font library
* http://www.henningkarlsen.com/electronics/r_fonts.php
*
*/
const unsigned char PROGMEM font[760] =
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // <Space>
0x18,0x3C,0x3C,0x18,0x18,0x00,0x18,0x00, // !
0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00, // "
0x6C,0x6C,0xFE,0x6C,0xFE,0x6C,0x6C,0x00, // #
0x18,0x3E,0x60,0x3C,0x06,0x7C,0x18,0x00, // $
0x00,0xC6,0xCC,0x18,0x30,0x66,0xC6,0x00, // %
0x38,0x6C,0x38,0x76,0xDC,0xCC,0x76,0x00, // &
0x18,0x18,0x30,0x00,0x00,0x00,0x00,0x00, // '
0x0C,0x18,0x30,0x30,0x30,0x18,0x0C,0x00, // (
0x30,0x18,0x0C,0x0C,0x0C,0x18,0x30,0x00, // )
0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00, // *
0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00, // +
0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30, // ,
0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00, // -
0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00, // .
0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00, // /
0x7C,0xC6,0xCE,0xD6,0xE6,0xC6,0x7C,0x00, // 0
0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00, // 1
0x7C,0xC6,0x06,0x1C,0x30,0x66,0xFE,0x00, // 2
0x7C,0xC6,0x06,0x3C,0x06,0xC6,0x7C,0x00, // 3
0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x1E,0x00, // 4
0xFE,0xC0,0xC0,0xFC,0x06,0xC6,0x7C,0x00, // 5
0x38,0x60,0xC0,0xFC,0xC6,0xC6,0x7C,0x00, // 6
0xFE,0xC6,0x0C,0x18,0x30,0x30,0x30,0x00, // 7
0x7C,0xC6,0xC6,0x7C,0xC6,0xC6,0x7C,0x00, // 8
0x7C,0xC6,0xC6,0x7E,0x06,0x0C,0x78,0x00, // 9
0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00, // :
0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x30, // ;
0x06,0x0C,0x18,0x30,0x18,0x0C,0x06,0x00, // <
0x00,0x00,0x7E,0x00,0x00,0x7E,0x00,0x00, // =
0x60,0x30,0x18,0x0C,0x18,0x30,0x60,0x00, // >
0x7C,0xC6,0x0C,0x18,0x18,0x00,0x18,0x00, // ?
0x7C,0xC6,0xDE,0xDE,0xDE,0xC0,0x78,0x00, // @
0x38,0x6C,0xC6,0xFE,0xC6,0xC6,0xC6,0x00, // A
0xFC,0x66,0x66,0x7C,0x66,0x66,0xFC,0x00, // B
0x3C,0x66,0xC0,0xC0,0xC0,0x66,0x3C,0x00, // C
0xF8,0x6C,0x66,0x66,0x66,0x6C,0xF8,0x00, // D
0xFE,0x62,0x68,0x78,0x68,0x62,0xFE,0x00, // E
0xFE,0x62,0x68,0x78,0x68,0x60,0xF0,0x00, // F
0x3C,0x66,0xC0,0xC0,0xCE,0x66,0x3A,0x00, // G
0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0x00, // H
0x3C,0x18,0x18,0x18,0x18,0x18,0x3C,0x00, // I
0x1E,0x0C,0x0C,0x0C,0xCC,0xCC,0x78,0x00, // J
0xE6,0x66,0x6C,0x78,0x6C,0x66,0xE6,0x00, // K
0xF0,0x60,0x60,0x60,0x62,0x66,0xFE,0x00, // L
0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0x00, // M
0xC6,0xE6,0xF6,0xDE,0xCE,0xC6,0xC6,0x00, // N
0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00, // O
0xFC,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00, // P
0x7C,0xC6,0xC6,0xC6,0xC6,0xCE,0x7C,0x0E, // Q
0xFC,0x66,0x66,0x7C,0x6C,0x66,0xE6,0x00, // R
0x7C,0xC6,0x60,0x38,0x0C,0xC6,0x7C,0x00, // S
0x7E,0x7E,0x5A,0x18,0x18,0x18,0x3C,0x00, // T
0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00, // U
0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x00, // V
0xC6,0xC6,0xC6,0xD6,0xD6,0xFE,0x6C,0x00, // W
0xC6,0xC6,0x6C,0x38,0x6C,0xC6,0xC6,0x00, // X
0x66,0x66,0x66,0x3C,0x18,0x18,0x3C,0x00, // Y
0xFE,0xC6,0x8C,0x18,0x32,0x66,0xFE,0x00, // Z
0x3C,0x30,0x30,0x30,0x30,0x30,0x3C,0x00, // [
0xC0,0x60,0x30,0x18,0x0C,0x06,0x02,0x00, // <Backslash>
0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00, // ]
0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00, // ^
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF, // _
0x30,0x18,0x0C,0x00,0x00,0x00,0x00,0x00, // '
0x00,0x00,0x78,0x0C,0x7C,0xCC,0x76,0x00, // a
0xE0,0x60,0x7C,0x66,0x66,0x66,0xDC,0x00, // b
0x00,0x00,0x7C,0xC6,0xC0,0xC6,0x7C,0x00, // c
0x1C,0x0C,0x7C,0xCC,0xCC,0xCC,0x76,0x00, // d
0x00,0x00,0x7C,0xC6,0xFE,0xC0,0x7C,0x00, // e
0x3C,0x66,0x60,0xF8,0x60,0x60,0xF0,0x00, // f
0x00,0x00,0x76,0xCC,0xCC,0x7C,0x0C,0xF8, // g
0xE0,0x60,0x6C,0x76,0x66,0x66,0xE6,0x00, // h
0x18,0x00,0x38,0x18,0x18,0x18,0x3C,0x00, // i
0x06,0x00,0x06,0x06,0x06,0x66,0x66,0x3C, // j
0xE0,0x60,0x66,0x6C,0x78,0x6C,0xE6,0x00, // k
0x38,0x18,0x18,0x18,0x18,0x18,0x3C,0x00, // l
0x00,0x00,0xEC,0xFE,0xD6,0xD6,0xD6,0x00, // m
0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x00, // n
0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0x00, // o
0x00,0x00,0xDC,0x66,0x66,0x7C,0x60,0xF0, // p
0x00,0x00,0x76,0xCC,0xCC,0x7C,0x0C,0x1E, // q
0x00,0x00,0xDC,0x76,0x60,0x60,0xF0,0x00, // r
0x00,0x00,0x7E,0xC0,0x7C,0x06,0xFC,0x00, // s
0x30,0x30,0xFC,0x30,0x30,0x36,0x1C,0x00, // t
0x00,0x00,0xCC,0xCC,0xCC,0xCC,0x76,0x00, // u
0x00,0x00,0xC6,0xC6,0xC6,0x6C,0x38,0x00, // v
0x00,0x00,0xC6,0xD6,0xD6,0xFE,0x6C,0x00, // w
0x00,0x00,0xC6,0x6C,0x38,0x6C,0xC6,0x00, // x
0x00,0x00,0xC6,0xC6,0xC6,0x7E,0x06,0xFC, // y
0x00,0x00,0x7E,0x4C,0x18,0x32,0x7E,0x00, // z
0x0E,0x18,0x18,0x70,0x18,0x18,0x0E,0x00, // {
0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00, // |
0x70,0x18,0x18,0x0E,0x18,0x18,0x70,0x00, // }
0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00, // ~
};
uint8_t charBuffer[8][8];
//Load char in buffer and return width in pixels
uint8_t loadCharInBuffer(char letter) {
uint8_t* tmpCharPix;
uint8_t tmpCharWidth;
int letterIdx = (letter - 32) * 8;
int x = 0; int y = 0;
for (int idx = letterIdx; idx < letterIdx + 8; idx++) {
for (int x = 0; x < 8; x++) {
charBuffer[x][y] = ((pgm_read_byte(&font[idx])) & (1 << (7 - x))) > 0;
}
y++;
}
tmpCharWidth = 8;
return tmpCharWidth;
}
void printText(char* text, unsigned int textLength, int xoffset, int yoffset, unsigned long color) {
uint8_t curLetterWidth = 0;
int curX = xoffset;
clearImg();
//Loop over all the letters in the string
for (int i = 0; i < textLength; i++) {
//Determine width of current letter and load its pixels in a buffer
curLetterWidth = loadCharInBuffer(text[i]);
//Loop until width of letter is reached
for (int lx = 0; lx < curLetterWidth; lx++) {
//Now copy column per column to field (as long as within the field
if (curX >= 12) { //If we are to far to the right, stop loop entirely
break;
} else if (curX >= 0) { //Draw pixels as soon as we are "inside" the drawing area
for (int ly = 0; ly < 8; ly++) { //Finally copy column
leds[pgm_read_byte(&ledsAdress[yoffset + ly][curX])] = charBuffer[lx][ly] * color;
}
}
curX++;
}
}
FastLED.show();
}
//Scroll current selection text from right to left;
void scrollText(char* curSelectionText, int curSelectionTextLength, unsigned long color ) {
for (int x = 12; x > -(curSelectionTextLength * 8); x--) {
printText(curSelectionText, curSelectionTextLength, x, 2, color);
delay(80);
}
}
const unsigned long mushroom[12][12] PROGMEM = {{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0x000000, 0x000000, 0x000000},
{0x000000, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0x000000},
{0x000000, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0x000000},
{0xDF0202, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xDF0202},
{0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202},
{0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202, 0xDF0202},
{0xDF0202, 0xDF0202, 0xFFFFFF, 0x000000, 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, 0x000000, 0xFFFFFF, 0xDF0202, 0xDF0202},
{0xDF0202, 0xFFFFFF, 0xFFFFFF, 0x000000, 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, 0x000000, 0xFFFFFF, 0xFFFFFF, 0xDF0202},
{0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000},
{0x000000, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0x000000},
{0x000000, 0x000000, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0x000000, 0x000000}
};
const unsigned long flower[12][12] PROGMEM = {{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0xFC3205, 0xFC3205, 0xFC3205, 0xFC3205, 0xFC3205, 0xFC3205, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0xFC3205, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFC3205, 0x000000, 0x000000},
{0x000000, 0xFC3205, 0xFAFE13, 0xFFFFFF, 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, 0xFFFFFF, 0xFAFE13, 0xFC3205, 0x000000},
{0x000000, 0xFC3205, 0xFAFE13, 0xFFFFFF, 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, 0xFFFFFF, 0xFAFE13, 0xFC3205, 0x000000},
{0x000000, 0x000000, 0xFC3205, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFC3205, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0xFC3205, 0xFC3205, 0xFC3205, 0xFC3205, 0xFC3205, 0xFC3205, 0x000000, 0x000000, 0x000000},
{0x000000, 0x0DFC03, 0x0DFC03, 0x000000, 0x000000, 0x0DFC03, 0x0DFC03, 0x000000, 0x000000, 0x0DFC03, 0x0DFC03, 0x000000},
{0x000000, 0x0DFC03, 0x0DFC03, 0x0DFC03, 0x000000, 0x0DFC03, 0x0DFC03, 0x000000, 0x0DFC03, 0x0DFC03, 0x0DFC03, 0x000000},
{0x000000, 0x000000, 0x0DFC03, 0x0DFC03, 0x0DFC03, 0x0DFC03, 0x0DFC03, 0x0DFC03, 0x0DFC03, 0x0DFC03, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x0DFC03, 0x0DFC03, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000}
};
const unsigned long star[12][12] PROGMEM = {{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFAFE13, 0xFAFE13, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0x000000},
{0x000000, 0x000000, 0xFAFE13, 0xFAFE13, 0x000000, 0xFAFE13, 0xFAFE13, 0x000000, 0xFAFE13, 0xFAFE13, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0xFAFE13, 0x000000, 0xFAFE13, 0xFAFE13, 0x000000, 0xFAFE13, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0x000000, 0x000000, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0x000000, 0x000000},
{0x000000, 0xFAFE13, 0xFAFE13, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFAFE13, 0xFAFE13, 0x000000}
};
const unsigned long beer[12][12] PROGMEM = {{0x020202, 0x020202, 0x020202, 0x020202, 0x020202, 0x020202, 0x020202, 0x020202, 0x020202, 0x020202, 0x020202, 0x020202},
{0x000000, 0x000000, 0x000000, 0xBEBEBD, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xBEBEBD, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0xBEBEBD, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xBEBEBD, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0xB49C04, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xB49C04, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0x000000},
{0x000000, 0x000000, 0x000000, 0xB49C04, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xB49C04, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0x000000},
{0x000000, 0x000000, 0x000000, 0xB49C04, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xB49C04, 0x000000, 0x000000, 0xFFFFFF, 0x000000},
{0x000000, 0x000000, 0x000000, 0xB49C04, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xB49C04, 0x000000, 0x000000, 0xFFFFFF, 0x000000},
{0x000000, 0x000000, 0x000000, 0xB49C04, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xB49C04, 0x000000, 0x000000, 0xFFFFFF, 0x000000},
{0x000000, 0x000000, 0x000000, 0xB49C04, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xB49C04, 0x000000, 0x000000, 0xFFFFFF, 0x000000},
{0x000000, 0x000000, 0x000000, 0xB49C04, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xB49C04, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0x000000},
{0x000000, 0x000000, 0x000000, 0xB49C04, 0xFAFE13, 0xFAFE13, 0xFAFE13, 0xA48E01, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0xB49C04, 0xB49C04, 0xB49C04, 0xB49C04, 0xB49C04, 0x000000, 0x000000, 0x000000, 0x000000}
};
const unsigned long PROGMEM menuError[12][12] = {{0xEF3101, 0xED2D01, 0x040100, 0x040100, 0x000000, 0x000000, 0x000000, 0x000000, 0x040100, 0x040100, 0xEB2801, 0xED2C01},
{0xED2C01, 0xEB2701, 0xE92202, 0x040100, 0x040100, 0x000000, 0x000000, 0x040100, 0x040100, 0xE81F02, 0xEA2301, 0xEB2701},
{0x040100, 0xE92202, 0xE81E01, 0xE61A01, 0x040000, 0x040000, 0x040000, 0x040000, 0xE61601, 0xE61A02, 0xE71D02, 0x040100},
{0x040100, 0x040100, 0xE61901, 0xE51501, 0xE31101, 0x040000, 0x040000, 0xE20F01, 0xE41202, 0xE51501, 0x040100, 0x040100},
{0x000000, 0x040000, 0x040000, 0xE31102, 0xE20D02, 0xE10A02, 0xE00902, 0xE10B02, 0xE20D02, 0x040000, 0x040000, 0x000000},
{0x000000, 0x000000, 0x040000, 0x040000, 0xE10B01, 0xDF0701, 0xDF0602, 0xE00702, 0x040000, 0x040000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x040000, 0x040000, 0xE10902, 0xDF0502, 0xDE0202, 0xDF0501, 0x040000, 0x040000, 0x000000, 0x000000},
{0x000000, 0x040000, 0x040000, 0xE30F02, 0xE10A02, 0xE00702, 0xE00502, 0xE00701, 0xE10A02, 0x040000, 0x040000, 0x000000},
{0x040100, 0x040000, 0xE61601, 0xE41102, 0xE20D02, 0x040000, 0x040000, 0xE10B02, 0xE20D02, 0xE41101, 0x040000, 0x040100},
{0x040100, 0xE81F01, 0xE71A01, 0xE51501, 0x040000, 0x040000, 0x040000, 0x040000, 0xE31202, 0xE51502, 0xE61A01, 0x040100},
{0xEB2801, 0xEA2301, 0xE81E01, 0x040000, 0x040000, 0x000000, 0x000000, 0x040000, 0x040000, 0xE71A01, 0xE81D01, 0xEA2301},
{0xED2C01, 0xEB2801, 0x040100, 0x040000, 0x000000, 0x000000, 0x000000, 0x000000, 0x040000, 0x040000, 0xE92202, 0xEB2701}
};
const unsigned long PROGMEM menuGameOfLife[12][12] = {{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x00FF00, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3},
{0xb2a24, 0x2cb194, 0xe281f, 0xc2720, 0xb261f, 0xb241e, 0x8231e, 0xa231f, 0x258d76, 0x8211d, 0x91e19, 0x91e19},
{0xb2823, 0xb2823, 0xa2723, 0x92621, 0x27a285, 0x7251d, 0x25977e, 0x7221d, 0x9201a, 0x9201a, 0x1e7f6b, 0x81b17},
{0x2bb292, 0x2aac90, 0x7281f, 0x27a486, 0xa231f, 0xc231b, 0xa2117, 0x238a75, 0x91f1c, 0x81d18, 0x91c1a, 0x1d775e},
{0x2aad8d, 0xc261d, 0xc251f, 0x9241d, 0x92320, 0xb211e, 0x228c76, 0x23876f, 0x91e19, 0x1e7b68, 0x81b17, 0x71a18},
{0x29aa8e, 0x27a387, 0x8261c, 0xc221f, 0x21967b, 0x248e77, 0x228a73, 0x1f826e, 0x1e7d67, 0x71c15, 0x71a16, 0x1d6c59},
{0xc2521, 0x26a286, 0x269b81, 0x28967d, 0x8211b, 0xa211b, 0x218470, 0xa1d19, 0x91c1a, 0x71c17, 0x81815, 0x61915},
{0xa2520, 0xa231f, 0x25977d, 0x9221c, 0x248a75, 0x1f8770, 0xa1f1a, 0x71c17, 0x81b17, 0x1d6e5b, 0x1a6956, 0x196251},
{0xa2520, 0x25977d, 0x8231c, 0x7221b, 0x92018, 0x22816d, 0x1f7c69, 0x1c7563, 0x91916, 0x61915, 0x196352, 0x71613},
{0x259a7f, 0x8231e, 0x8211b, 0x91f1c, 0x20836e, 0x91e17, 0x71c17, 0x91916, 0x1a6b5a, 0x1a6455, 0x81712, 0x61512},
{0xb211e, 0x82219, 0x228974, 0x22836f, 0x1e7f6b, 0x1d7962, 0x71b19, 0x1b6c59, 0x1a6755, 0x186150, 0x175b4c, 0x514f}
};
const unsigned long PROGMEM menuPong[12][12] = {{0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3},
{0x000000, 0x000000, 0xFF0000, 0xFF0000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0xFF0000, 0xFF0000, 0x000000, 0x000000}
};
const unsigned long PROGMEM menuTetris[12][12] = {{0x00a651, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0xFF0000, 0xFF0000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0xFF0000, 0x000000, 0x000000},
{0xFF0000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0xFF0000, 0x4DBB2E, 0x000000},
{0xFF0000, 0xFF0000, 0x4DBB2E, 0x4DBB2E, 0x000000, 0x000000, 0xD4BD0D, 0xD4BD0D, 0xD4BD0D, 0x4DBB2E, 0x4DBB2E, 0xFF0000},
{0xFF0000, 0x0DA5D4, 0x4DBB2E, 0x4DBB2E, 0xFF0000, 0x000000, 0x4DBB2E, 0xD4BD0D, 0x0DA5D4, 0x0DA5D4, 0x4DBB2E, 0xFF0000},
{0x0DA5D4, 0x0DA5D4, 0x0DA5D4, 0x000000, 0xFF0000, 0x4DBB2E, 0x4DBB2E, 0x000000, 0x0DA5D4, 0xD4BD0D, 0xD4BD0D, 0xFF0000},
{0xD4BD0D, 0xD4BD0D, 0xD4BD0D, 0xD4BD0D, 0xFF0000, 0xFF0000, 0x4DBB2E, 0x000000, 0x0DA5D4, 0xD4BD0D, 0xD4BD0D, 0xFF0000}
};
const unsigned long PROGMEM menuSnake[12][12] = {{0x000000, 0x000000, 0x000000, 0xF26522, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x388FEC, 0x388FEC, 0x000000, 0xFF0000, 0x000000, 0x388FEC, 0x388FEC, 0x388FEC, 0x000000, 0x000000, 0x388FEC, 0x388FEC},
{0x000000, 0x388FEC, 0x000000, 0x000000, 0x000000, 0x388FEC, 0x000000, 0x388FEC, 0x000000, 0x000000, 0x388FEC, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x388FEC, 0x000000, 0x388FEC, 0x388FEC, 0x388FEC, 0x388FEC, 0x000000},
{0x000000, 0x000000, 0x388FEC, 0x000000, 0x000000, 0x388FEC, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x388FEC, 0x000000, 0x000000, 0x388FEC, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x388FEC, 0x388FEC, 0x388FEC, 0x388FEC, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000}
};
const unsigned long PROGMEM menuSimon[12][12] =
{{0x000000, 0x388FEC, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3},
{0xFFF202, 0xFFF202, 0xFFF20C, 0xFFF202, 0xFFF202, 0x000000, 0x000000, 0xFF0202, 0xFF191A, 0xFF2727, 0xFF1A1A, 0xFF0202},
{0xFFF202, 0xFFF234, 0xFFF257, 0xFFF235, 0xFFF202, 0x000000, 0x000000, 0xFF191A, 0xFF4C4C, 0xFF6767, 0xFF4C4C, 0xFF1A19},
{0xFFF20C, 0xFFF257, 0xFFF898, 0xFFF257, 0xFFF20C, 0x000000, 0x000000, 0xFF2727, 0xFF6767, 0xFF9797, 0xFF6767, 0xFF2726},
{0xFFF202, 0xFFF235, 0xFFF257, 0xFFF234, 0xFFF202, 0x000000, 0x000000, 0xFF1919, 0xFF4C4C, 0xFF6767, 0xFF4C4C, 0xFF1A19},
{0xFFF202, 0xFFF202, 0xFFF20C, 0xFFF202, 0xFFF202, 0x000000, 0x000000, 0xFF0202, 0xFF1A19, 0xFF2626, 0xFF1A1A, 0xFF0202},
{0x34BA20, 0x34BA20, 0x34BA20, 0x34BA20, 0x34BA20, 0x000000, 0x000000, 0x2760B9, 0x4360B9, 0x4F68B9, 0x4360B9, 0x2760B9},
{0x34BA20, 0x48BA40, 0x6BBA63, 0x48BA41, 0x34BA20, 0x000000, 0x000000, 0x4360B9, 0x6D87B9, 0x819ABE, 0x6D86B9, 0x4360B9},
{0x34BA20, 0x6BBA63, 0xACE2A4, 0x6BBA63, 0x34BA20, 0x000000, 0x000000, 0x4F68B9, 0x819ABE, 0xA4BEE1, 0x819BBE, 0x4F68B9},
{0x34BA20, 0x49BA40, 0x6BBA63, 0x48BA41, 0x34BA20, 0x000000, 0x000000, 0x4360B9, 0x6D86B9, 0x819BBE, 0x6D86B9, 0x4360B9},
{0x34BA20, 0x34BA20, 0x34BA20, 0x34BA20, 0x34BA20, 0x000000, 0x000000, 0x2760B9, 0x4360B9, 0x4F68B9, 0x4360B9, 0x2760B9}
};
const unsigned long PROGMEM menuBottle[12][12] =
{{0x000000, 0x000000, 0x000000, 0x000000, 0xFFF200, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x02F900, 0x0EFA00, 0x1FFB01},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x33FC01, 0x41FE00, 0x4BFE00},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x7BFF02, 0x7AFE02, 0x79FF01},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000}
};
const unsigned long PROGMEM menuParty[12][12] = {{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x960055, 0x960055, 0x000000, 0x000000, 0x000000, 0x000000},
{0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3},
{0x00A651, 0x00A651, 0x00A651, 0x00A651, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x00A651, 0x00A651},
{0xEC008C, 0xEC008C, 0xEC008C, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xEC008C, 0xEC008C, 0xEC008C},
{0xF7941D, 0xF7941D, 0xF7941D, 0xF7941D, 0xF7941D, 0xF7941D, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x00AEEF, 0x00AEEF, 0x00AEEF, 0x00AEEF, 0x00AEEF, 0x00AEEF, 0x000000},
{0xFFF200, 0xFFF200, 0xFFF200, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFFF200, 0xFFF200, 0xFFF200},
{0x000000, 0x000000, 0xED1C24, 0xED1C24, 0xED1C24, 0xED1C24, 0xED1C24, 0xED1C24, 0x000000, 0x000000, 0x000000, 0x000000},
{0x2E3192, 0x2E3192, 0x2E3192, 0x2E3192, 0x2E3192, 0x2E3192, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x00A651, 0x00A651, 0x00A651, 0x00A651, 0x00A651, 0x00A651},
{0x000000, 0x000000, 0x000000, 0xF7941D, 0xF7941D, 0xF7941D, 0xF7941D, 0xF7941D, 0xF7941D, 0x000000, 0x000000, 0x000000},
{0xEC008C, 0xEC008C, 0xEC008C, 0xEC008C, 0xEC008C, 0xEC008C, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000}
};
const unsigned long PROGMEM menuImage[12][12] = {{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0xFF0000, 0x000000, 0x000000},
{0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0xAC8255, 0xFBBD7B, 0xE8AF72, 0xE8AF72, 0xE8AF72, 0xE8AF72, 0xE8AF72, 0xEAB276, 0xE7A45D, 0x9E6C36, 0x000000},
{0x000000, 0xAD7F4F, 0xF1ECCE, 0xD6FFFF, 0xD4FFFF, 0xDCFFFF, 0xFAFFF9, 0xFFFF3E, 0xFFD529, 0xF4A05C, 0x9D6B36, 0x000000},
{0x000000, 0xAD7F4F, 0xF1ECCE, 0xD6FFFF, 0xD4FFFF, 0xDCFFFF, 0xFAFFFA, 0xFFFF42, 0xFFFF00, 0xF4D31A, 0x9D683A, 0x000000},
{0x000000, 0xAB8959, 0xFF5638, 0xFF0000, 0xFF0000, 0xFF2020, 0xFFDFDF, 0xFFFFFF, 0xFFFFFF, 0xF4D3B0, 0x9D6830, 0x000000},
{0x000000, 0xBD8B4E, 0xFF5731, 0xFF0000, 0xFF0000, 0xFF0000, 0xFE0505, 0xE1BDBE, 0xD5FFFF, 0xDDD3B0, 0x9E6830, 0x000000},
{0x000000, 0xB68959, 0x734338, 0x007100, 0x00BF00, 0x001920, 0x0066DF, 0x0088FF, 0x0080FF, 0x5F89B0, 0xA76E30, 0x000000},
{0x000000, 0xB67F59, 0x73EC38, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FD05, 0x00A1BE, 0x0076FF, 0x5F89B0, 0xA76E30, 0x000000},
{0x000000, 0x9F6D38, 0xD38B3D, 0xC7843C, 0xC7843C, 0xC7843C, 0xC7843C, 0xC7843C, 0xC7843C, 0xD38B3D, 0x9F6D38, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000}
};
const unsigned long PROGMEM menuSpectrum[12][12] = {{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xffff00},
{0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x531A8D, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x5E2598, 0x000000, 0x2585A9, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x3696B9, 0x591F93, 0x2585A9, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x642B9E, 0x3696B9, 0x3389AA, 0x2585A9, 0x000000, 0x490F83, 0x000000, 0x000000, 0x000000},
{0x000000, 0x6F36A9, 0x000000, 0x3E9EC1, 0x3C92B3, 0x3389AA, 0x2585A9, 0x4E1488, 0x16769A, 0x000000, 0x000000, 0x000000},
{0x743BAE, 0x4EAED2, 0x000000, 0x3E9DC1, 0x3C92B3, 0x3289AA, 0x2A80A1, 0x217898, 0x167699, 0x000000, 0x000000, 0x000000},
{0x60B7D7, 0x4EAED2, 0x000000, 0x3E9DC1, 0x3C92B3, 0x3289AA, 0x2A80A0, 0x217798, 0x16769A, 0x000000, 0x40077A, 0x000000},
{0x60B7D7, 0x4EAED2, 0x6A30A4, 0x3E9EC1, 0x3C92B3, 0x3289AA, 0x2A80A1, 0x217898, 0x167699, 0x000000, 0x0A698D, 0x3D0376},
{0x60B7D7, 0x57AECE, 0x4FA5C5, 0x459CBC, 0x3C92B3, 0x3389A9, 0x2A80A1, 0x217898, 0x167699, 0x440B7E, 0x09698D, 0x065C7C}
};
const unsigned long PROGMEM menuText[12][12] = {{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFFF200, 0x000000},
{0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3, 0xFDCAD3},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0xFF0000, 0xFF0000, 0x000000, 0x000000},
{0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0x000000},
{0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0x000000},
{0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000},
{0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0x000000},
{0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFF0000, 0xFF0000, 0xFF0000, 0x000000, 0x000000},
{0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000}
};
byte scorePlayerLeft = 0;
byte scorePlayerRight = 0;
byte positionPlayerLeft;
byte positionPlayerRight;
uint8_t ballx;
uint8_t previousBallx;
uint8_t bally;
uint8_t previousBally;
uint8_t velocityx;
uint8_t velocityy;
byte ballBounces;
byte gameSpeed;
//Arduino : Unsigned long !!!!!
unsigned long lastAutoPlayerMoveTime;
unsigned long rumbleUntil;
unsigned long waitUntil;
void setPosition() {
if (!digitalRead(R1_PIN)) {
if (positionPlayerLeft + (3 - 1) / 2 < 12 - 1) {
++positionPlayerLeft;
}
}
else if (!digitalRead(L1_PIN)) {
if (positionPlayerLeft - 3 / 2 > 0) {
--positionPlayerLeft;
}
}
if (!digitalRead(L2_PIN)) {
if (positionPlayerRight - 3 / 2 > 0) {
--positionPlayerRight;
}
}
else if (!digitalRead(R2_PIN)) {
if (positionPlayerRight + (3 - 1) / 2 < 12 - 1) {
++positionPlayerRight;
}
}
curControl = 'n';
}
void checkBallHitByPlayer() {
if (ballx == 1)
{
if (bally == positionPlayerLeft)
{
velocityx = 1;
ballx = 1;
++ballBounces;
rumbleUntil = curTime + 200;
}
else if (bally < positionPlayerLeft && bally >= positionPlayerLeft - 3 / 2)
{
velocityx = 1;
velocityy = max(-1, velocityy - 1);
ballx = 1;
bally = positionPlayerLeft - 3 / 2 - 1;
++ballBounces;
rumbleUntil = curTime + 200;
}
else if (bally > positionPlayerLeft && bally <= positionPlayerLeft + (3 - 1) / 2)
{
velocityx = 1;
velocityy = min(1, velocityy + 1);
ballx = 1;
bally = positionPlayerLeft + (3 - 1) / 2 + 1;
++ballBounces;
rumbleUntil = curTime + 200;
}
}
else if (ballx == 12 - 2)
{
if (bally == positionPlayerRight)
{
velocityx = -1;
ballx = 12 - 2;
++ballBounces;
}
else if (bally < positionPlayerRight && bally >= positionPlayerRight - 3 / 2)
{
velocityx = -1;
velocityy = max(-1, velocityy - 1);
ballx = 12 - 2;
bally = positionPlayerRight - 3 / 2 - 1;
++ballBounces;
}
else if (bally > positionPlayerRight && bally <= positionPlayerRight + (3 - 1) / 2)
{
velocityx = -1;
velocityy = min(1, velocityy + 1);
ballx = 12 - 2;
bally = positionPlayerRight + (3 - 1) / 2 + 1;
++ballBounces;
}
}
}
void checkBallOutOfBounds() {
if (bally < 1)
{
velocityy = - velocityy;
bally = 0;
//bally = 1;
} else if (bally > 12 -1)
{
velocityy = - velocityy;
bally = 12 - 2;
//bally = 12-1;
}
if (ballx < 0)
{
velocityx = - velocityx;
velocityy = 0;
ballx = 12 / 2;
bally = 12 / 2;
++scorePlayerRight;
ballBounces = 0;
waitUntil = curTime + 2000;
fadeImg();
}
else if (ballx > 12 - 1)
{
velocityx = - velocityx;
velocityy = 0;
ballx = 12 / 2;
bally = 12 / 2;
++scorePlayerLeft;
ballBounces = 0;
waitUntil = curTime + 2000;
fadeImg();
}
}
/*boolean moveAutoPlayer()
{
if (bally < positionPlayerRight)
{
if (positionPlayerRight - 3 / 2 > 0)
{
--positionPlayerRight;
return true;
}
}
else if (bally > positionPlayerRight)
{
if (positionPlayerRight + (3 - 1) / 2 < 12 - 1)
{
++positionPlayerRight;
return true;
}
}
return false;
}*/
void pongInit() {
scorePlayerLeft = 0;
scorePlayerRight = 0;
positionPlayerLeft = 12 / 2;
positionPlayerRight = 12 / 2;
ballx = 12 / 2;
bally = 12 / 2;
velocityx = 1;
velocityy = 0;
ballBounces = 0;
gameSpeed = 180;
lastAutoPlayerMoveTime = 0;
rumbleUntil = 0;
waitUntil = 0;
}
void runPong() {
pongInit();
boolean pongRunning = true;
while (pongRunning) {
if (scorePlayerLeft == 5) {
pongRunning = false;
scrollText("Game over", 9, 0xff0000);
scorePlayerLeft = 0;
scorePlayerRight = 0;
break;
} if (scorePlayerRight == 5) {
pongRunning = false;
scrollText("Game over", 9, 0xff0000);
scorePlayerLeft = 0;
scorePlayerRight = 0;
break;
}
checkBallHitByPlayer();
/* if ((curTime - lastAutoPlayerMoveTime) > 200) {
if (moveAutoPlayer()) {
lastAutoPlayerMoveTime = curTime;
}
}*/
ballx += velocityx;
bally += velocityy;
checkBallOutOfBounds();
for (byte i = 0; i < 12; i++) {
for (byte j = 0; j < 12; j++) {
leds[pgm_read_byte(&ledsAdress[i][j])] = 0x000000;
}
}
// Draw ball
leds[pgm_read_byte(&ledsAdress[ballx][bally])] = 0xffffff;
// Draw player left
for (int y = positionPlayerLeft - 3 / 2; y <= positionPlayerLeft + 3 / 2; ++y) {
leds[pgm_read_byte(&ledsAdress[0][y])] = 0x0000ff;
}
// Draw player right
for (int y = positionPlayerRight - 3 / 2; y <= positionPlayerRight + 3 / 2; ++y) {
leds[pgm_read_byte(&ledsAdress[12 - 1][y])] = 0xffff00;
}
FastLED.show();
boolean dirChanged = false;
do {
if (menuValidation) {
pongRunning = false;
break;
}
readPointer();
if (curControl != 'n' && !dirChanged) { //Can only change direction once per loop
dirChanged = true;
setPosition();
}
curTime = millis();
}
while ((curTime - prevUpdateTime) < 140); //Once enough time has passed, proceed. The lower this number, the faster the game is
prevUpdateTime = curTime;
}
}
byte curLength;//Curren length of snake
int xs[127];//Array containing all snake segments,
int ys[127];// max snake length is array length
char dir;//Current Direction of snake
boolean snakeGameOver;
byte ax = 0;//Apple x position
byte ay = 0;//Apple y position
void snakeInit() {
//Snake start position and direction & initialise variables
curLength = 3;
xs[0] = 3;
xs[1] = 2;
xs[2] = 1;
ys[0] = 12 / 2;
ys[1] = 12 / 2;
ys[2] = 12 / 2;
dir = 'd';
//Generate random apple position
ax = random(0, 11);
ay = random(0, 11);
snakeGameOver = false;
}
/* Set direction from current button state */
void setDirection() {
readPointer();
switch (curControl) {
case 'q':
dir = 'z';
break;
case 'd':
dir = 's';
break;
case 's':
dir = 'd';
break;
case 'z':
dir = 'q';
break;
}
}
/* Ending, show score */
void die() {
snakeGameOver = true;
fadeImg();
}
/* Collision detection function */
boolean collide(int x1, int x2, int y1, int y2, int w1, int w2, int h1, int h2) {
if ((x1 + w1 > x2) && (x1 < x2 + w2) && (y1 + h1 > y2) && (y1 < y2 + h2)) {
return true;
} else {
return false;
}
}
void runSnake() {
snakeInit();
prevUpdateTime = 0;
boolean snakeRunning = true;
while (snakeRunning) {
//Check self-collision with snake
int i = curLength - 1;
while (i >= 2) {
if (collide(xs[0], xs[i], ys[0], ys[i], 1, 1, 1, 1)) {
die();
}
i = i - 1;
}
if (snakeGameOver) {
snakeRunning = false;
break;
}
//Check collision of snake head with apple
if (collide(xs[0], ax, ys[0], ay, 1, 1, 1, 1)) {
//Increase score and snake length;
curLength = curLength + 1;
//Add snake segment with temporary position of new segments
xs[curLength - 1] = 255;
ys[curLength - 1] = 255;
//Generate new apple position
ax = random(0, 11);
ay = random(0, 11);
for (int j = 0; j < curLength; j++) {
if (collide(ax, xs[j], ay, ys[j], 1, 1, 1, 1)) {
ax = random(0, 11);
ay = random(0, 11);
j = 0; //ICI
}
}
}
//Shift snake position array by one
i = curLength - 1;
while (i >= 1) {
xs[i] = xs[i - 1];
ys[i] = ys[i - 1];
i = i - 1;
}
//Determine new position of head of snake
if (dir == 'd') {
xs[0] = xs[0] + 1;
} else if (dir == 'q') {
xs[0] = xs[0] - 1;
} else if (dir == 'z') {
ys[0] = ys[0] - 1;
} else {//DOWN
ys[0] = ys[0] + 1;
}
//Check if outside playing field
if ((xs[0] < 0) || (xs[0] >= 12) || (ys[0] < 0) || (ys[0] >= 12)) {
if (xs[0] < 0) {
xs[0] = 12 - 1;
} else if (xs[0] >= 12) {
xs[0] = 0;
} else if (ys[0] < 0) {
ys[0] = 12 - 1;
} else if (ys[0] >= 12) {
ys[0] = 0;
}
}
for (int j = 0; j < 144; j++) {
leds[j] = 0x000000;
}
//Draw apple
leds[pgm_read_byte(&ledsAdress[ax][ay])] = 0xFF0000;
//Draw snake
for (int j = 0; j < curLength; j++) {
leds[pgm_read_byte(&ledsAdress[xs[j]] [ys[j]])] = 0x388FEC;
}
FastLED.show();
//Check buttons and set snake movement direction while we are waiting to draw the next move
curTime = 0;
boolean dirChanged = false;
do {
if (menuValidation) {
snakeRunning = false;
break;
}
if (curControl != 'n' && !dirChanged) {//Can only change direction once per loop
dirChanged = true;
setDirection();
}
curTime = millis();
} while ((curTime - prevUpdateTime) < 200);//Once enough time has passed, proceed. The lower this number, the faster the game is
prevUpdateTime = curTime;
}
}
// Playing field
byte selectedColor = 0;
const unsigned long PROGMEM colorLib[5] = {0x000000, 0xff0000, 0xffff00, 0x00ff00, 0x03cdff};
struct Field {
boolean pix[12][12 + 1]; //Make field one larger so that collision detection with bottom of field can be done in a uniform way
byte color[12][12];
};
Field field;
//Structure to represent active brick on screen
struct Brick {
boolean enabled;//Brick is disabled when it has landed
byte xpos, ypos;
byte yOffset;//Y-offset to use when placing brick at top of field
byte siz;
boolean pix[4][4];
byte color;
};
Brick activeBrick;
//Struct to contain the different choices of blocks
struct AbstractBrick {
byte yOffset;//Y-offset to use when placing brick at top of field
byte siz;
byte pix[4][4];
};
//Brick "library"
AbstractBrick brickLib[7] = {
{
1,//yoffset when adding brick to field
4,
{ {0, 0, 0, 0},
{0, 1, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}
}
},
{
0,
4,
{ {0, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 0, 0}
}
},
{
1,
3,
{ {0, 0, 0, 0},
{1, 1, 1, 0},
{0, 0, 1, 0},
{0, 0, 0, 0}
}
},
{
1,
3,
{ {0, 0, 1, 0},
{1, 1, 1, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
}
},
{
1,
3,
{ {0, 0, 0, 0},
{1, 1, 1, 0},
{0, 1, 0, 0},
{0, 0, 0, 0}
}
},
{
1,
3,
{ {0, 1, 1, 0},
{1, 1, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
}
},
{
1,
3,
{ {1, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 0, 0},
{0, 0, 0, 0}
}
}
};
uint16_t brickSpeed;
byte nbRowsThisLevel;
uint16_t nbRowsTotal;
Brick tmpBrick;
boolean tetrisRunning = false;
boolean tetrisGameOver;
/* *** Game functions *** */
//Check collision between bricks in the field and the specified brick
boolean checkFieldCollision(struct Brick* brick) {
byte bx, by;
byte fx, fy;
for (by = 0; by < 4; by++) {
for (bx = 0; bx < 4; bx++) {
fx = (*brick).xpos + bx;
fy = (*brick).ypos + by;
if (( (*brick).pix[bx][by] == 1)
&& ( field.pix[fx][fy] == 1)) {
return true;
}
}
}
return false;
}
void newActiveBrick() {
// byte selectedBrick = 3;
byte selectedBrick = random(7);
++selectedColor;
if (selectedColor > 3) {
selectedColor = 0;
}
//Set properties of brick
activeBrick.siz = brickLib[selectedBrick].siz;
activeBrick.yOffset = brickLib[selectedBrick].yOffset;
activeBrick.xpos = 12 / 2 - activeBrick.siz / 2;
activeBrick.ypos = -1 - activeBrick.yOffset;
activeBrick.enabled = true;
//Set color of brick
switch (selectedColor) {
case 0 :
activeBrick.color = 1;
break;
case 1 :
activeBrick.color = 2;
break;
case 2 :
activeBrick.color = 3;
break;
case 3 :
activeBrick.color = 4;
break;
}
//activeBrick.color = colorLib[1];
//Copy pix array of selected Brick
for (byte y = 0; y < 4; y++) {
for (byte x = 0; x < 4; x++) {
activeBrick.pix[x][y] = (brickLib[selectedBrick]).pix[x][y];
}
}
//Check collision, if already, then game is over
if (checkFieldCollision(&activeBrick)) {
tetrisGameOver = true;
}
}
//Check collision between specified brick and all sides of the playing field
boolean checkSidesCollision(struct Brick* brick) {
//Check vertical collision with sides of field
byte fx, fy;
for (byte by = 0; by < 4; by++) {
for (byte bx = 0; bx < 4; bx++) {
if ( (*brick).pix[bx][by] == 1) {
fx = (*brick).xpos + bx;//Determine actual position in the field of the current pix of the brick
fy = (*brick).ypos + by;
if (fx < 0 || fx >= 12) {
return true;
}
}
}
}
return false;
}
void printField() {
for (byte x = 0; x < 12; x++) {
for (byte y = 0; y < 12; y++) {
boolean activeBrickPix = 0;
if (activeBrick.enabled) { //Only draw brick if it is enabled
//Now check if brick is "in view"
if ((x >= activeBrick.xpos) && (x < (activeBrick.xpos + (activeBrick.siz)))
&& (y >= activeBrick.ypos) && (y < (activeBrick.ypos + (activeBrick.siz)))) {
activeBrickPix = (activeBrick.pix)[x - activeBrick.xpos][y - activeBrick.ypos];
}
}
if (field.pix[x][y] == 1) {
leds[pgm_read_byte(&ledsAdress[y][x])] = pgm_read_dword(&colorLib[field.color[x][y]]);
} else if (activeBrickPix == 1) {
leds[pgm_read_byte(&ledsAdress[y][x])] = pgm_read_dword(&colorLib[activeBrick.color]);
} else {
leds[pgm_read_byte(&ledsAdress[y][x])] = 0x000000;
}
}
}
FastLED.show();
}
void rotateActiveBrick() {
//Copy active brick pix array to temporary pix array
for (byte y = 0; y < 4; y++) {
for (byte x = 0; x < 4; x++) {
tmpBrick.pix[x][y] = activeBrick.pix[x][y];
}
}
tmpBrick.xpos = activeBrick.xpos;
tmpBrick.ypos = activeBrick.ypos;
tmpBrick.siz = activeBrick.siz;
//Depending on size of the active brick, we will rotate differently
if (activeBrick.siz == 3) {
//Perform rotation around center pix
tmpBrick.pix[0][0] = activeBrick.pix[0][2];
tmpBrick.pix[0][1] = activeBrick.pix[1][2];
tmpBrick.pix[0][2] = activeBrick.pix[2][2];
tmpBrick.pix[1][0] = activeBrick.pix[0][1];
tmpBrick.pix[1][1] = activeBrick.pix[1][1];
tmpBrick.pix[1][2] = activeBrick.pix[2][1];
tmpBrick.pix[2][0] = activeBrick.pix[0][0];
tmpBrick.pix[2][1] = activeBrick.pix[1][0];
tmpBrick.pix[2][2] = activeBrick.pix[2][0];
//Keep other parts of temporary block clear
tmpBrick.pix[0][3] = 0;
tmpBrick.pix[1][3] = 0;
tmpBrick.pix[2][3] = 0;
tmpBrick.pix[3][3] = 0;
tmpBrick.pix[3][2] = 0;
tmpBrick.pix[3][1] = 0;
tmpBrick.pix[3][0] = 0;
} else if (activeBrick.siz == 4) {
//Perform rotation around center "cross"
tmpBrick.pix[0][0] = activeBrick.pix[0][3];
tmpBrick.pix[0][1] = activeBrick.pix[1][3];
tmpBrick.pix[0][2] = activeBrick.pix[2][3];
tmpBrick.pix[0][3] = activeBrick.pix[3][3];
tmpBrick.pix[1][0] = activeBrick.pix[0][2];
tmpBrick.pix[1][1] = activeBrick.pix[1][2];
tmpBrick.pix[1][2] = activeBrick.pix[2][2];
tmpBrick.pix[1][3] = activeBrick.pix[3][2];
tmpBrick.pix[2][0] = activeBrick.pix[0][1];
tmpBrick.pix[2][1] = activeBrick.pix[1][1];
tmpBrick.pix[2][2] = activeBrick.pix[2][1];
tmpBrick.pix[2][3] = activeBrick.pix[3][1];
tmpBrick.pix[3][0] = activeBrick.pix[0][0];
tmpBrick.pix[3][1] = activeBrick.pix[1][0];
tmpBrick.pix[3][2] = activeBrick.pix[2][0];
tmpBrick.pix[3][3] = activeBrick.pix[3][0];
}
//Now validate by checking collision.
//Collision possibilities:
// -Brick now sticks outside field
// -Brick now sticks inside fixed bricks of field
//In case of collision, we just discard the rotated temporary brick
if ((!checkSidesCollision(&tmpBrick)) && (!checkFieldCollision(&tmpBrick))) {
//Copy temporary brick pix array to active pix array
for (byte y = 0; y < 4; y++) {
for (byte x = 0; x < 4; x++) {
activeBrick.pix[x][y] = tmpBrick.pix[x][y];
}
}
}
}
//Copy active pixels to field, including color
void addActiveBrickToField() {
byte fx, fy;
for (byte by = 0; by < 4; by++) {
for (byte bx = 0; bx < 4; bx++) {
fx = activeBrick.xpos + bx;
fy = activeBrick.ypos + by;
if (fx >= 0 && fy >= 0 && fx < 12 && fy < 12 && activeBrick.pix[bx][by]) { //Check if inside playing field
//field.pix[fx][fy] = field.pix[fx][fy] || activeBrick.pix[bx][by];
field.pix[fx][fy] = activeBrick.pix[bx][by];
field.color[fx][fy] = activeBrick.color;
}
}
}
}
//Shift brick left/right/down by one if possible
void shiftActiveBrick(char dir) {
//Change position of active brick (no copy to temporary needed)
if (dir == 'q') {
activeBrick.xpos--;
} else if (dir == 'd') {
activeBrick.xpos++;
} else if (dir == 's') {
activeBrick.ypos++;
}
//Check position of active brick
//Two possibilities when collision is detected:
// -Direction was LEFT/RIGHT, just revert position back
// -Direction was DOWN, revert position and fix block to field on collision
//When no collision, keep activeBrick coordinates
if ((checkSidesCollision(&activeBrick)) || (checkFieldCollision(&activeBrick))) {
//Serial.println("coll");
if (dir == 'q') {
activeBrick.xpos++;
} else if (dir == 'd') {
activeBrick.xpos--;
} else if (dir == 's') {
activeBrick.ypos--;//Go back up one
addActiveBrickToField();
activeBrick.enabled = false;//Disable brick, it is no longer moving
}
}
}
//Move all pix from te field above startRow down by one. startRow is overwritten
void moveFieldDownOne(byte startRow) {
if (startRow == 0) { //Topmost row has nothing on top to move...
return;
}
byte x, y;
for (y = startRow - 1; y > 0; y--) {
for (x = 0; x < 12; x++) {
field.pix[x][y + 1] = field.pix[x][y];
field.color[x][y + 1] = field.color[x][y];
}
}
}
void checkFullLines() {
int x, y;
int minY = 0;
for (y = (12 - 1); y >= minY; y--) {
byte rowSum = 0;
for (x = 0; x < 12; x++) {
rowSum = rowSum + (field.pix[x][y]);
}
if (rowSum >= 12) {
//Found full row, animate its removal
for (x = 0; x < 12; x++) {
field.pix[x][y] = 0;
printField();
delay(100);
}
//Move all upper rows down by one
moveFieldDownOne(y);
y++; minY++;
printField();
delay(100);
nbRowsThisLevel++; nbRowsTotal++;
if (nbRowsThisLevel >= 2) {
nbRowsThisLevel = 0;
brickSpeed = brickSpeed - 100;
if (brickSpeed < 200) {
brickSpeed = 200;
}
}
}
}
}
void clearField() {
for (byte y = 0; y < 12; y++) {
for (byte x = 0; x < 12; x++) {
field.pix[x][y] = 0;
field.color[x][y] = 0;
}
}
for (byte x = 0; x < 12; x++) { //This last row is invisible to the player and only used for the collision detection routine
field.pix[x][12] = 1;
}
}
void playerControlActiveBrick() {
switch (curControl) {
case 'd':
shiftActiveBrick('d');
break;
case 'q':
shiftActiveBrick('q');
break;
case 's':
shiftActiveBrick('s');
break;
case 'a':
rotateActiveBrick();
break;
case 'y':
tetrisRunning = false;
break;
}
}
void tetrisInit() {
clearField();
brickSpeed = 1000;
nbRowsThisLevel = 0;
nbRowsTotal = 0;
tetrisGameOver = false;
newActiveBrick();
}
void runTetris(void) {
tetrisInit();
prevUpdateTime = 0;
tetrisRunning = true;
while (tetrisRunning) {
curTime = 0;
do {
readPointer(); //ATTENTION SOURCE BUG ?!
if (menuValidation) {
tetrisRunning = false;
break;
}
if (curControl != 'n') {
playerControlActiveBrick();
printField();
curControl = 'n';
}
if (tetrisGameOver) break;
curTime = millis();
} while ((curTime - prevUpdateTime) < brickSpeed);//Once enough time has passed, proceed. The lower this number, the faster the game is
prevUpdateTime = curTime;
if (tetrisGameOver) {
fadeImg();
//Disable loop and exit to main menu of led table
tetrisRunning = false;
break;
}
//If brick is still "on the loose", then move it down by one
if (activeBrick.enabled) {
shiftActiveBrick('s');
} else {
//Active brick has "crashed", check for full lines
//and create new brick at top of field
checkFullLines();
newActiveBrick();
prevUpdateTime = millis();//Reset update time to avoid brick dropping two spaces
}
printField();
}
}
/*
Written by FischiMc and SupaStefe, modified by Antoine Rochebois (scaling to 12*12)
This sketch uses a 10x10 RGB LED-Matrix as a spectrum analyzer
It uses a FTT Library to analyze an audio signal connected to the
pin A7 of an Arduino nano. Everytime a column gets higher than
10 pixels the color of each column changes.
*/
#define LOG_OUT 0 //set output of FFT library to linear not logarithmical
#define LIN_OUT 1
#define FFT_N 256 //set to 256 point fft
#include <FFT.h> //include the FFT library
#include <FastLED.h> //include the FastLED Library
#include <math.h> //include library for mathematic funcions
#define DATA_PIN 5 //DATA PIN WHERE YOUR LEDS ARE CONNECTED
#define NUM_LEDS 144 //amount of LEDs in your matrix
CRGB leds[NUM_LEDS];
float faktoren[12] = {1, 1.1, 1.15, 1.25, 1.35, 1.45, 1.55, 1.65 , 1.75, 1.8, 2, 3}; //factors to increase the height of each column
unsigned char hs[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 , 0}; //height of each column
float hue = 0; //hue value of the colors
void setBalken(unsigned char column, unsigned char height) { //calculation of the height of each column
unsigned char h = (unsigned char)map(height, 0, 255, 0, 12);
h = (unsigned char)(h * faktoren[column]);
if (h < hs[column]) {
hs[column]--;
}
else if (h > hs[column]) {
hs[column] = h;
}
if (height > 250) {
hue += 2; //CHANGE THIS VALUE IF YOU WANT THE DIFFERENCE BETWEEN THE COLORS TO BE BIGGER
if (hue > 25) hue = 0;
}
for (unsigned char y = 0; y < 12; y++) { //set colors of pixels according to column and hue
if (hs[column] > y) {
if (column % 2 == 0) {
leds[y + (column * 12)] = CHSV((hue * 12) + (column * 12), 255, 200);
} else {
leds[12 - y + (column * 12)] = CHSV((hue * 12) + (column * 12), 255, 200);
}
} else {
if (column % 2 == 0) {
leds[y + (column * 12)] = CRGB::Black;
} else {
leds[12 - y + (column * 12)] = CRGB::Black;
}
}
}
}
unsigned char grenzen[13] = {0, 3, 5, 7, 9, 11, 13, 15, 17, 20, 24, 32, 69}; //borders of the frequency areas
void setup() {
FastLED.addLeds<WS2812B, DATA_PIN, GRB> (leds, NUM_LEDS);
TIMSK0 = 0; //turn off timer0 for lower jitter
ADCSRA = 0xe5; //set the adc to free running mode
ADMUX = 0x40; //use pin A0
DIDR0 = 0x01; //turn off the digital input for
analogReference(EXTERNAL); //set aref to external
}
void loop() {
while (1) { //reduces jitter
cli(); //UDRE interrupt slows this way down on arduino1.0
for (int i = 0 ; i < 512 ; i += 2) { //save 256 samples
while (!(ADCSRA & 0x10)); //wait for adc to be ready
ADCSRA = 0xf5; //restart adc
byte m = ADCL; //fetch adc data
byte j = ADCH;
int k = (j << 8) | m; //form into an int
k -= 0x0200; //form into a signed int
k <<= 6; //form into a 16b signed int
fft_input[i] = k; //put real data into even bins
}
fft_window(); // window the data for better frequency response
fft_reorder(); // reorder the data before doing the fft
fft_run(); // process the data in the fft
fft_mag_lin(); // take the output of the fft
sei();
fft_lin_out[0] = 0;
fft_lin_out[1] = 0;
for (unsigned char i = 0; i < 13; i++) {
unsigned char maxW = 0;
for (unsigned char x = grenzen[i]; x < grenzen[i + 1]; x++) {
if ((unsigned char)fft_lin_out[x] > maxW) {
maxW = (unsigned char)fft_lin_out[x];
}
}
setBalken(i, maxW);
}
TIMSK0 = 1;
FastLED.show();
TIMSK0 = 0;
}
}
Comments