Sign in
Sign In
Getting Started
Device Manager
Digital
Store
Sign In
NOW CHROME OS READY!
Share
<iframe framebor
Add the following snippet to your HTML:
Make an interactive table that displays games, an audio spectrum, and animations on a 12x12
built-in LED matrix.
36,966 views
12 comments
123 respects
Project showcase
acrylic
animation
app
arcade
audio
bluetooth
board
cheap
display
foam
game
hc06
hc-06
ikea
inventor
lack
life
matrix
meter
mit
play
pong
printing
screen
simon
snake
spectrum
table
tetris
text
ws2812b
36,605 views
12 comments
122 respects
Prototype PCB × 1
Too many cables... × 1
405x405mm white acrylic glass × 1
Black foam board (A2 - 5mm thick) × 1
5V - 50W power supply × 1
L profiled aluminium bar × 1
Box cutter
Wood rasp
Saw
Drill
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. Presentation
In 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.
After 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...
A 40Hz sin wave as it is read by the Arduino (It's saturating a little bit but this is
goodenough)
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.
The final look of the interface (Menu/validation button, arduino USB, Audio IO, power
switch)
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.
I used the MIT App Inventor software to make a very simple Android application to control my
table through Bluetooth.
6. TO DO :
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 ;)
I need to setup these aluminium bars to finish the table (red tape is temporary)
Code
Arduino main code
Game of Life
Random roulette mode (idk how to call it ^^' )
decoration.h
font.h
imgMario.h
imgMenu.h
pong.h
snake.h
tetris.h
Audio spectrum visualizer
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.
/*
*
* 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>
//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;
}
} 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]);
}
}
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() {
/*
*
* RUN AT STARTUP
*
*/
void setup() {
//Setup the BT connection
BT.begin(9600);
/*
*
* RUN PERPETUALY
*
*/
void loop() {
Game of LifeArduino
} 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;
}
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));
decoration.hC/C++
/*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;
}
}
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() {
}*/
font.hC/C++
uint8_t charBuffer[8][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();
}
}
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);
}
}
imgMario.hArduino
imgMenu.hArduino
pong.hArduino
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();
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();
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;
}
}
snake.hArduino
boolean snakeGameOver;
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();
}
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;
}
//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;
}
tetris.hArduino
// 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;
byte color;
};
Brick activeBrick;
//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;
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;
//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;
} 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];
}
//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();
//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();
}
} 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;
setBalken(i, maxW);
}
TIMSK0 = 1;
FastLED.show();
TIMSK0 = 0;
}
}
Download
On this panel you'll find the main control buttons, audio IO and the USB port used to upload the
code on the Arduino Nano.
Made on Fusion360
Comments
Author
Antoine Rochebois
1 project
23 followers
Follow
Additional contributors
Published on
Table of contents
Respect project
Write a comment
Share
Arbalet is a touch table made of 150 LEDs. Play Tetris Snake or Piano on your coffee table, or
develop your own Python apps!
7,887 views
1 comment
38 respects
Create an awesome looking motion activated RGB lights for any staircase under $20!
27,618 views
19 comments
99 respects
Tthis project is going to show you how to make Bluetooth led control with lcd beside the
Arduino sowing the new led status.
Bluetooth control led with lcd led status display real time.
17,312 views
4 comments
34 respects
Name on a box that lights up and has three different patterns that provide a cool light show.
1,872 views
1 comment
5 respects
"Otto DIY with steroids" + Bluetooth + APP + switch + touch sensors + strength + sound
detection...
48,222 views
117 comments
162 respects
Using 64 LED to create a cube, it separates to 4 layers and every layer have 16 LED which is
connecting each other layer.
1,608 views
0 comments
0 respects
Powered by