/*
Tetris for Arduino Uno/Nano
Fernando Jerez 2017
License Creative Commons - Attribution
https://www.thingiverse.com/thing:2676560
2020-05-23: John Bradnam
Make: https://www.hackster.io/john-bradnam/wii-chuck-neopixel-tetris-game-047fdc
Removed servo and replaced with 7 WS2812B leds
Removed switches and replaced with Wii Nunchuck
Added buzzer for sound effects
*/
#include <Wire.h>
#include <ArduinoNunchuk.h>
#include <FastLED.h>
#include <LedControl.h>
/**************
PINOUT
**************/
#define MATRIX_PIN 5
#define MATRIX_PIXELS 128
#define MATRIX_BRIGHTNESS 8
#define NEXT_PIN 9
#define NEXT_PIXELS 7
#define NEXT_BRIGHTNESS 255
#define DATA 2
#define CLOCK 4
#define LOAD 3
#define SPEAKER A0
LedControl lc=LedControl(DATA,CLOCK,LOAD,1);
ArduinoNunchuk nunchuk = ArduinoNunchuk();
//#define ACCEL
#define FRAMES_PER_SECOND 20
CRGB leds[MATRIX_PIXELS];
byte board[MATRIX_PIXELS];
CRGB nextLeds[NEXT_PIXELS];
int nextCount = 0;
int speed = 5; //lower one every 5 frames (smaller = faster)
int frameCount = 0;
boolean gameover = true;
// PIECES
#define EMPTY 7
typedef struct {
int width; // width
int height; // height
int picture[6]; // picture
int turns; // number of possible turns
int cx; // center of rotation X
int cy; // center y
CRGB color; // color
int next; // LED number for Next LED strip
} Piece;
Piece pieces[7] = {
{3, 2, {0, 1, 0, 1, 1, 1}, 4, 1, 1, CRGB(255, 0, 0), 0 }, // T
{3, 2, {0, 1, 1, 1, 1, 0}, 2, 1, 0, CRGB(255, 70, 0), 1}, // S
{2, 3, {1, 0, 1, 0, 1, 1}, 4, 0, 1, CRGB(255, 255, 0), 2}, // L
{4, 1, {1, 1, 1, 1}, 2, 2, 0, CRGB(0, 255, 0), 3}, // I
{2, 3, {0, 1, 0, 1, 1, 1}, 4, 1, 1, CRGB(0, 255, 255), 4}, // J
{3, 2, {1, 1, 0, 0, 1, 1}, 2, 1, 0, CRGB(0, 0, 255), 5}, // Z
{2, 2, {1, 1, 1, 1}, 1, 0, 0, CRGB(255, 0, 255), 6} // O
};
int rotation = 0; //0,1,2,3
int npiece, next;
int xpos, ypos;
// Avoid the auto-click
boolean pressed1 = false, pressed2 = false;
boolean joy1 = false, joy2 = false;
int pause1 = 5, pause2 = 5; // Delay for joystick movements
// Points
long points = 0;
int lines = 0;
int lastAnalogX = 0;
int lastAnalogY = 0;
int lastAccelX = 0;
int lastAccelY = 0;
int lastzButton = 0; //Big button
int lastcButton = 0; //Small button
void setup()
{
Serial.begin(115200);
delay(2000); // 2 second delay for recovery
pinMode(SPEAKER,OUTPUT);
lc.shutdown(0, false);
lc.setIntensity(0, 8);
lc.clearDisplay(0);
lc.setDigit(0, 7, 3, false);
FastLED.addLeds<NEOPIXEL, MATRIX_PIN>(leds, MATRIX_PIXELS);
FastLED.addLeds<NEOPIXEL, NEXT_PIN>(nextLeds, NEXT_PIXELS);
FastLED.setBrightness(MATRIX_BRIGHTNESS);
nunchuk.init();
cleanTable();
npiece = random(0, 7);
next = random(0, 7);
xpos = 4;
ypos = pieces[npiece].cy;
rotation = 0;
}
void loop()
{
writeNumber(points); // Write points on 7-Led display
//TIMSK0 &= ~TOIE0;
nunchuk.update();
//displayNunchuckValues();
//TIMSK0 |= TOIE0;
int jx = nunchuk.analogX;
int jy = nunchuk.analogY;
if (!gameover)
{
/*
GAME
*/
paintBoard();
frameCount++;
if (jx == 255)
{
pause1 = max(0, pause1 - 1);
if (joy1 == false || pause1 == 0)
{
joy1 = true;
beepTurn();
if (checkColision(npiece, xpos + 1, ypos, rotation))
{
xpos++;
}
}
}
else
{
pause1 = 5;
joy1 = false;
}
if (jx == 0)
{
pause2 = max(0, pause2 - 1);
if (joy2 == false || pause2 == 0)
{
joy2 = true;
beepTurn();
if (checkColision(npiece, xpos - 1, ypos, rotation))
{
xpos--;
}
}
}
else
{
pause2 = 5;
joy2 = false;
}
if (jy == 0)
{
if (checkColision(npiece, xpos, ypos + 1, rotation))
{
ypos++;
points++;
frameCount = 1;
beepTurn();
}
}
if (nunchuk.zButton == HIGH)
{
if (!pressed1)
{
int nrot = (rotation + 1) % pieces[npiece].turns;
if (checkColision(npiece, xpos, ypos, nrot))
{
rotation = nrot;
}
pressed1 = true;
beepTurn();
}
}
else
{
pressed1 = false;
}
if (nunchuk.cButton == HIGH)
{
if (!pressed2)
{
int nrot = (rotation + pieces[npiece].turns - 1) % pieces[npiece].turns;
if (checkColision(npiece, xpos, ypos, nrot))
{
rotation = nrot;
}
pressed2 = true;
beepTurn();
}
}
else
{
pressed2 = false;
}
paintPiece(npiece, xpos, ypos, rotation);
// Low
if (frameCount % speed == 0)
{
frameCount = 1;
if (checkColision(npiece, xpos, ypos + 1, rotation))
{
ypos++;
}
else
{
// paint on board
paintOnBoard(npiece, xpos, ypos, rotation);
// Check lines
checkBoardLines();
// Take out new piece
npiece = next;
next = random(0, 7);
xpos = 4;
ypos = pieces[npiece].cy;
rotation = 0;
clearNextLeds(false);
nextLeds[pieces[npiece].next] = pieces[npiece].color;
FastLED[1].showLeds(NEXT_BRIGHTNESS);
FastLED[0].showLeds(MATRIX_BRIGHTNESS);
beepRelease();
delay(200);
// Check that it doesn't crash (if GAME OVER crashes)
if (!checkColision(npiece, xpos, ypos, rotation))
{
// GAME OVER
gameover = true;
paintOnBoard(npiece, xpos, ypos, rotation);
paintPiece(npiece, xpos, ypos, rotation);
// servo a 90
clearNextLeds(true);
//servo.write(map(90, 0, 180, SERVO_MIN, SERVO_MAX));
playLoseMusic();
}
}
}
}
else
{
// GAME OVER
// Press A / START to start
if (jy == 255)
{
FastLED.clear();
gameover = false;
points = 0;
lines = 0;
clearNextLeds(true);
//servo.write(map(pieces[next].servo, 0, 180, SERVO_MIN, SERVO_MAX));
}
frameCount++;
if (frameCount % 5 == 0)
{
frameCount = 0;
for (int i = MATRIX_PIXELS - 1; i >= 0; i--)
{
board[i] = EMPTY;
// Fade out
// leds[i].r = max(leds[i].r-10,0);
// leds[i].g = max(leds[i].g-10,0);
// leds[i].b = max(leds[i].b-10,0);
// Scroll down
if (i >= 8) {
leds[i].r = leds[i - 8].r;
leds[i].g = leds[i - 8].g;
leds[i].b = leds[i - 8].b;
} else {
leds[i] = CRGB(0, 0, 0);
}
}
boolean fits = true;
int p, px, py, pr;
do {
npiece = next;
next = random(0, 7);
pr = random(0, 5);
px = random(0, 8);
py = 1;//random(0,16);
} while (!checkColision(next, px, py, pr));
clearNextLeds(false);
int nx = (nextCount < NEXT_PIXELS) ? nextCount : NEXT_PIXELS * 2 - nextCount - 1;
nextLeds[pieces[nx].next] = pieces[nx].color;
nextCount = (nextCount + 1) % (NEXT_PIXELS * 2);
FastLED[1].showLeds(NEXT_BRIGHTNESS);
paintPiece(next, px, py, pr);
}
}
// send the 'leds' array out to the actual LED strip
FastLED[0].showLeds(MATRIX_BRIGHTNESS);
// insert a delay to keep the framerate modest
//FastLED.delay(1000 / FRAMES_PER_SECOND);
delay(1000 / FRAMES_PER_SECOND);
}
/* Paint the piece in the LED matrix */
void paintPiece(int piece, int xpos, int ypos, int rot)
{
for (int i = 0; i < pieces[piece].width; i++)
{
for (int j = 0; j < pieces[piece].height; j++)
{
switch (rot)
{
case 0:
if (pieces[piece].picture[i + j * pieces[piece].width] == 1) {
leds[(ypos + j - pieces[piece].cy) * 8 + (xpos + i - pieces[piece].cx)] = pieces[piece].color;
}
break;
case 1:
if (pieces[piece].picture[i + j * pieces[piece].width] == 1)
{
leds[(ypos + i - pieces[piece].cx) * 8 + (xpos - j + pieces[piece].cy)] = pieces[piece].color;
}
break;
case 2:
if (pieces[piece].picture[i + j * pieces[piece].width] == 1)
{
leds[(ypos - j + pieces[piece].cy) * 8 + (xpos - i + pieces[piece].cx)] = pieces[piece].color;
}
break;
case 3:
if (pieces[piece].picture[i + j * pieces[piece].width] == 1)
{
leds[(ypos - i + pieces[piece].cx) * 8 + (xpos + j - pieces[piece].cy)] = pieces[piece].color;
}
break;
}
}
}
}
// Paint the piece on the board (In memory buffer)
void paintOnBoard(int piece, int xpos, int ypos, int rot)
{
for (int i = 0; i < pieces[piece].width; i++)
{
for (int j = 0; j < pieces[piece].height; j++)
{
switch (rot)
{
case 0:
if (pieces[piece].picture[i + j * pieces[piece].width] == 1)
{
board[(ypos + j - pieces[piece].cy) * 8 + (xpos + i - pieces[piece].cx)] = (byte)piece;
}
break;
case 1:
if (pieces[piece].picture[i + j * pieces[piece].width] == 1)
{
board[(ypos + i - pieces[piece].cx) * 8 + (xpos - j + pieces[piece].cy)] = (byte)piece;
}
break;
case 2:
if (pieces[piece].picture[i + j * pieces[piece].width] == 1) {
board[(ypos - j + pieces[piece].cy) * 8 + (xpos - i + pieces[piece].cx)] = (byte)piece;
}
break;
case 3:
if (pieces[piece].picture[i + j * pieces[piece].width] == 1)
{
board[(ypos - i + pieces[piece].cx) * 8 + (xpos + j - pieces[piece].cy)] = (byte)piece;
}
break;
}
}
}
}
boolean checkColision(int piece, int xpos, int ypos, int rot)
{
// returns false if it crashes, true if not
for (int i = 0; i < pieces[piece].width; i++)
{
for (int j = 0; j < pieces[piece].height; j++)
{
switch (rot)
{
case 0:
if (pieces[piece].picture[i + j * pieces[piece].width] == 1)
{
int xx = xpos + i - pieces[piece].cx;
int yy = ypos + j - pieces[piece].cy;
if (xx < 0 || xx > 7 || yy < 0 || yy > 15) return false;
if (board[xx + yy * 8] != EMPTY) return false;
}
break;
case 1:
if (pieces[piece].picture[i + j * pieces[piece].width] == 1)
{
int xx = xpos - j + pieces[piece].cy;
int yy = ypos + i - pieces[piece].cx;
if (xx < 0 || xx > 7 || yy < 0 || yy > 15) return false;
if (board[xx + yy * 8] != EMPTY) return false;
}
break;
case 2:
if (pieces[piece].picture[i + j * pieces[piece].width] == 1)
{
int xx = xpos - i + pieces[piece].cx;
int yy = ypos - j + pieces[piece].cy;
if (xx < 0 || xx > 7 || yy < 0 || yy > 15) return false;
if (board[xx + yy * 8] != EMPTY) return false;
}
break;
case 3:
if (pieces[piece].picture[i + j * pieces[piece].width] == 1)
{
int xx = xpos + j - pieces[piece].cy;
int yy = ypos - i + pieces[piece].cx;
if (xx < 0 || xx > 7 || yy < 0 || yy > 15) return false;
if (board[xx + yy * 8] != EMPTY) return false;
}
break;
}
}
}
return true;
}
// Clean full board lines
void checkBoardLines()
{
boolean complete;
int account = 0;
for (int y = 15; y >= 0; y--)
{
do {
complete = true;
for (int x = 0; x < 8; x++)
{
if (board[x + y * 8] == EMPTY) complete = false;
}
if (complete)
{
account++;
lines++;
removeLine(y);
// low board
for (int yy = y; yy >= 0; yy--)
{
for (int xx = 0; xx < 8; xx++)
{
if (yy == 0)
{
board[xx] = EMPTY;
}
else
{
board[xx + yy * 8] = board[xx + (yy - 1) * 8];
}
}
}
paintBoard();
FastLED[0].showLeds(MATRIX_BRIGHTNESS);
}
} while (complete);
}
if (account >= 1)
{
// 50,150,400,900
switch (account)
{
case 1: points += 50; break;
case 2: points += 150; break;
case 3: points += 400; break;
case 4: points += 900; break;
}
//points +=pow(10,account);
// Update speed
speed = max(0, 5 - floor(lines / 10));
}
}
/* Animation deleting line */
void removeLine(int y)
{
for (int x = 0; x < 8; x++)
{
leds[x + y * 8] = CRGB(100, 0, 0);
FastLED[0].showLeds(MATRIX_BRIGHTNESS);
delay(50);
leds[x + y * 8] = CRGB(0, 0, 0);
}
}
/* Paint the board in the LED matrix */
void paintBoard()
{
for (int i = 0; i < MATRIX_PIXELS; i++)
{
if (board[i] != EMPTY)
{
leds[i] = pieces[board[i]].color;
}
else
{
leds[i] = CRGB(0, 0, 0);
}
}
}
// Fill the board with EMPTY
void cleanTable()
{
for (int i = 0; i < MATRIX_PIXELS; i++)
{
board[i] = EMPTY;
}
}
//Turn off all the top LEDs
void clearNextLeds(bool update)
{
for (int i = 0; i < NEXT_PIXELS; i++)
{
nextLeds[i] = CRGB(0, 0, 0);
}
if (update)
{
FastLED[1].showLeds(NEXT_BRIGHTNESS);
}
}
void writeNumber(long n)
{
byte i = n % 10;
lc.setDigit(0, 0, i, false);
if (n >= 10) lc.setDigit(0, 1, (n / 10) % 10, false); else lc.setChar(0, 1, ' ', false);
if (n >= 100) lc.setDigit(0, 2, (n / 100) % 10, false); else lc.setChar(0, 2, ' ', false);
if (n >= 1000) lc.setDigit(0, 3, (n / 1000) % 10, false); else lc.setChar(0, 3, ' ', false);
if (n >= 10000) lc.setDigit(0, 4, (n / 10000) % 10, false); else lc.setChar(0, 4, ' ', false);
if (n >= 100000) lc.setDigit(0, 5, (n / 100000) % 10, false); else lc.setChar(0, 5, ' ', false);
if (n >= 1000000) lc.setDigit(0, 6, (n / 1000000) % 10, false); else lc.setChar(0, 6, ' ', false);
if (n >= 10000000) lc.setDigit(0, 7, (n / 10000000) % 10, false); else lc.setChar(0, 7, ' ', false);
}
//Turn on and off buzzer quickly
void beepRelease()
{
digitalWrite(SPEAKER, HIGH); // turn on buzzer
delay(20);
digitalWrite(SPEAKER, LOW); // turn off buzzer
}
//Turn on and off buzzer quickly
void beepTurn()
{
digitalWrite(SPEAKER, HIGH); // turn on buzzer
delay(5);
digitalWrite(SPEAKER, LOW); // turn off buzzer
}
//Play wah wah wah wahwahwahwahwahwah
void playLoseMusic()
{
delay(400);
//wah wah wah wahwahwahwahwahwah
for(double wah=0; wah<4; wah+=6.541)
{
playSound(440+wah, 50);
}
playSound(466.164, 100);
delay(80);
for(double wah=0; wah<5; wah+=4.939)
{
playSound(415.305+wah, 50);
}
playSound(440.000, 100);
delay(80);
for(double wah=0; wah<5; wah+=4.662)
{
playSound(391.995+wah, 50);
}
playSound(415.305, 100);
delay(80);
for(int j=0; j<7; j++)
{
playSound(391.995, 70);
playSound(415.305, 70);
}
delay(400);
}
//Play a sound of a frequency in Hz for a duration in mS
void playSound(double freqHz, int durationMs)
{
//Calculate the period in microseconds
int periodMicro = int((1/freqHz)*1000000);
int halfPeriod = periodMicro/2;
//store start time
long startTime = millis();
//(millis() - startTime) is elapsed play time
while((millis() - startTime) < durationMs)
{
digitalWrite(SPEAKER, HIGH);
delayMicroseconds(halfPeriod);
digitalWrite(SPEAKER, LOW);
delayMicroseconds(halfPeriod);
}
}
Comments