/********************************************
* Arduino TETRIS *
* for TVout libraries *
* *
* Roberto Melzi - October the 23nd, 2016 *
* *
* Version alpha 01 *
* *
********************************************/
#include <TVout.h>
#include <TVoutfonts/fontALL.h>
#define BUTTON_fast A4
#define BUTTON_right A1
#define BUTTON_rotationL A2
#define BUTTON_left A3
#define BUTTON_rotationR A5
#define IN_GAME 0 //in game state
#define IN_MENU 1 //in menu state
#define GAME_OVER 2 //game over state
TVout TV;
void setup() {
//Serial.begin(9600);
//TV.begin(_PAL); //for devices with only 1k sram(m168) use TV.begin(_NTSC,128,56) - not tested
TV.begin(PAL, 128, 64); // --------- max resolution = 128 x 96 ----------------------------------
randomSeed(analogRead(5));
}
boolean button_1 = 0;
boolean button_2 = 0;
boolean button_3 = 0;
boolean button_4 = 0;
boolean button_5 = 0;
int block[4][2]={{0,0},{0,0},{0,0},{0,0}};
int blockExt[4][2]={{0,0},{0,0},{0,0},{0,0}};
int blockOld[4][2]={{0,0},{0,0},{0,0},{0,0}};
int blockTmp[4][2]={{0,0},{0,0},{0,0},{0,0}};
int blockTr[4][2]={{0,0},{0,0},{0,0},{0,0}};
int yLine[4] = {0,0,0,0};
int yLineTmp[4] = {0,0,0,0};
int yCounter = 0;
int x = 60;
int y = 6;
int score;
int scoreTot;
int scoreTotOld;
int noLoop = -1;
int clock = 1;
int delta = 0;
int colorOld;
int blockN;
int blockNext;
int busy;
int noDelete = 0;
int k = 0;
int counterMenu = 0;
unsigned long time = 0;
int fast = 15;
int deltaT = 150;
void processInputs() { // ------------------- this reads the buttons and add a small deley if a button remains pressed ----------------
if (button_1 == 0 && button_2 == 0 && button_3 == 0 && button_4 == 0 && button_5 == 0){
button_1 = digitalRead(A0); //right
button_2 = digitalRead(A2); //rotationR
button_3 = digitalRead(A1); //left
button_4 = digitalRead(A4); //fast falling
button_5 = digitalRead(A3); //rotationL
//delay(deltaT);
delay(10 + fast); // -------- to be optimized ------------------------
}
else{
if(button_4 == 1){
//button_4 = digitalRead(A4);
clearInputs();
//delay(fast);
}
else{
clearInputs();
delay(deltaT);
}
}
}
void clearInputs() {
button_1 = 0;
button_2 = 0;
button_3 = 0;
button_4 = 0;
button_5 = 0;
}
void drawMenu() {
while (button_1 == 0 && button_1 == 0 && button_2 == 0 && button_3 == 0) {
processInputs();
TV.select_font(font8x8);
TV.print(40, 15, "TETRIS");
TV.select_font(font6x8); //-------------- TV.select_font(font4x6); ------------
TV.print(18, 35, "by Roberto Melzi");
TV.delay(100);
counterMenu++;
}
TV.clear_screen();
drawGameScreen();
drawScore(score);
}
void drawScore(int i) {
if (i > 39){
score = 0;
i = 0;
fast = fast - 2;
if (fast < 1) {fast = 1;}
}
TV.draw_line(20, 50, 20, 10, 0);
TV.draw_line(20, 50, 20, 50 - i, 1);
TV.draw_line(19, 10, 22, 10, 1);
TV.draw_line(19, 50, 22, 50, 1);
TV.select_font(font6x8);
TV.draw_rect(99, 53, 20, 6, 0, 0);
TV.print(99, 53, scoreTot);
scoreTotOld = scoreTot;
}
void drawBorder() {
// total screen size = 128x64 - max 128x96
TV.draw_line(44,2,78,2,1);
TV.draw_line(44,61,78,61,1);
TV.draw_line(44,2,44,61,1);
TV.draw_line(78,2,78,61,1);
TV.select_font(font6x8);
TV.print(87, 42, "score");
}
// --------------------- this is for the beginning game window ------------------------
void drawStartScreen() {
drawBorder();
drawGameScreen();
delay(200);
}
// ---------------------- this is the main function to draw the game screen -----------
void drawGameScreen() {
drawBorder();
}
void toneSafe(int frequency, int lenght){ // ---------- tone gives problems with the interrupts, use toneSafe instead --------
TV.tone(frequency, lenght);
TV.delay(lenght);
TV.noTone();
}
// ----------------------- Tetriminos definition --------------------------------------
void blockDef(int i) {
if (i == 1){
// O
block[0][0] = 0;
block[0][1] = 0;
block[1][0] = 1;
block[1][1] = 0;
block[2][0] = 0;
block[2][1] = 1;
block[3][0] = 1;
block[3][1] = 1;
}
if (i == 2){
// L
block[0][0] = -1;
block[0][1] = 0;
block[1][0] = 0;
block[1][1] = 0;
block[2][0] = 1;
block[2][1] = 0;
block[3][0] = -1;
block[3][1] = 1;
}
if (i == 3){
// J
block[0][0] = -1;
block[0][1] = 0;
block[1][0] = 0;
block[1][1] = 0;
block[2][0] = 1;
block[2][1] = 0;
block[3][0] = 1;
block[3][1] = 1;
}
if (i == 4){
// I
block[0][0] = -1;
block[0][1] = 0;
block[1][0] = 0;
block[1][1] = 0;
block[2][0] = 1;
block[2][1] = 0;
block[3][0] = 2;
block[3][1] = 0;
}
if (i == 5){
// S
block[0][0] = -1;
block[0][1] = 0;
block[1][0] = 0;
block[1][1] = 0;
block[2][0] = 0;
block[2][1] = 1;
block[3][0] = 1;
block[3][1] = 1;
}
if (i == 6){
// Z
block[0][0] = -1;
block[0][1] = 1;
block[1][0] = 0;
block[1][1] = 1;
block[2][0] = 0;
block[2][1] = 0;
block[3][0] = 1;
block[3][1] = 0;
}
if (i == 7){
// T
block[0][0] = -1;
block[0][1] = 0;
block[1][0] = 0;
block[1][1] = 0;
block[2][0] = 0;
block[2][1] = 1;
block[3][0] = 1;
block[3][1] = 0;
}
}
// -------------------------- expansion for 16:9 monitors ------------------------------
void blockExtension() {
blockExt[0][0] = block[0][0]*3;
blockExt[0][1] = block[0][1]*2;
blockExt[1][0] = block[1][0]*3;
blockExt[1][1] = block[1][1]*2;
blockExt[2][0] = block[2][0]*3;
blockExt[2][1] = block[2][1]*2;
blockExt[3][0] = block[3][0]*3;
blockExt[3][1] = block[3][1]*2;
}
// -------------------------- Tetraminos rotation ------------------------------
void blockRotation(int clock){
blockOld[0][0] = block[0][0];
blockOld[0][1] = block[0][1];
blockOld[1][0] = block[1][0];
blockOld[1][1] = block[1][1];
blockOld[2][0] = block[2][0];
blockOld[2][1] = block[2][1];
blockOld[3][0] = block[3][0];
blockOld[3][1] = block[3][1];
block[0][0] = blockOld[0][1]*clock;
block[0][1] = -blockOld[0][0]*clock;
block[1][0] = blockOld[1][1]*clock;
block[1][1] = -blockOld[1][0]*clock;
block[2][0] = blockOld[2][1]*clock;
block[2][1] = -blockOld[2][0]*clock;
block[3][0] = blockOld[3][1]*clock;
block[3][1] = -blockOld[3][0]*clock;
}
// -------------------------- Tetraminos translation ------------------------------
void blockTranslation(int x, int y) {
blockTr[0][0] = blockExt[0][0] + x;
blockTr[0][1] = blockExt[0][1] + y;
blockTr[1][0] = blockExt[1][0] + x;
blockTr[1][1] = blockExt[1][1] + y;
blockTr[2][0] = blockExt[2][0] + x;
blockTr[2][1] = blockExt[2][1] + y;
blockTr[3][0] = blockExt[3][0] + x;
blockTr[3][1] = blockExt[3][1] + y;
}
void delBlock(){
if (noDelete == 1) {noDelete = 0;}
else {
for (int i = 0; i < 4; i++){
TV.draw_line(blockTr[i][0],blockTr[i][1],blockTr[i][0] + 3,blockTr[i][1],0);
TV.draw_line(blockTr[i][0],blockTr[i][1] + 1,blockTr[i][0] + 3,blockTr[i][1] + 1,0);
}
}
}
void drawBlock(){
for (int i = 0; i < 4; i++){
TV.draw_line(blockTr[i][0],blockTr[i][1],blockTr[i][0] + 3,blockTr[i][1], 1);
TV.draw_line(blockTr[i][0],blockTr[i][1] + 1,blockTr[i][0] + 3,blockTr[i][1] + 1, 1);
}
for (int i = 0; i < 4; i++){
blockTmp[0][0] = blockTr[0][0];
blockTmp[0][1] = blockTr[0][1];
blockTmp[1][0] = blockTr[1][0];
blockTmp[1][1] = blockTr[1][1];
blockTmp[2][0] = blockTr[2][0];
blockTmp[2][1] = blockTr[2][1];
blockTmp[3][0] = blockTr[3][0];
blockTmp[3][1] = blockTr[3][1];
}
}
void drawBlockTmp(){
for (int i = 0; i < 4; i++){
TV.draw_line(blockTmp[i][0],blockTmp[i][1],blockTmp[i][0] + 3,blockTmp[i][1], 1);
TV.draw_line(blockTmp[i][0],blockTmp[i][1] + 1,blockTmp[i][0] + 3,blockTmp[i][1] + 1, 1);
}
}
void checkBlock(){
busy = 0;
for (int i = 0; i < 4; i++){
busy = busy + TV.get_pixel(blockTr[i][0], blockTr[i][1]) + TV.get_pixel(blockTr[i][0] + 2, blockTr[i][1]);
}
}
void replaceBlock(){
blockExtension();
blockTranslation(x, y);
checkBlock();
if (busy == 0){
drawBlock();
}
else // ---------- else is true when the block cannot get down -----------------
{
drawBlockTmp();
checkForFullLine(); // ---- check if the line is filled when the block cannot get down anymore ----------------------
noLoop = 0;
noDelete = 1;
if (y < 8) {
gameOver();
}
}
}
void gameOver(){ // ------------------------------------------- Game Over ! --------------------------------------------
noLoop = -1;
score = 0;
scoreTot = 0;
scoreTotOld = 0;
fast = 15;
clearInputs();
time = 0;
TV.delay(300);
toneSafe(660, 200);
toneSafe(330, 200);
toneSafe(165, 200);
toneSafe(82, 200);
TV.clear_screen();
TV.select_font(font8x8);
TV.print(43,20,"GAME");
TV.print(53,35,"OVER");
TV.delay(600);
while (button_4 == 0) {
processInputs();
}
clearInputs();
TV.clear_screen();
}
void draw_fullRect (int x1, int y1, int x2, int y2, int color){
if (y2 < y1){ int y3 = y1; y1 = y2; y2 = y3; }
for (int i = y1; i <= y2; i++){
TV.draw_line(x1, i, x2, i, color);
}
}
void drawBlockNext(){ // ----- draw next block on the right side --------------------------------
blockExtension();
blockTranslation(100, 10);
draw_fullRect(95, 2, 112, 15, 0);
drawBlock();
}
void checkBlockTranslation(){
x = x + delta;
blockExtension();
blockTranslation(x, y);
checkBlock();
if (busy == 0){
drawBlock();
}
else
{
x = x - delta;
blockExtension();
blockTranslation(x, y);
drawBlock();
}
}
void checkBlockRotation(){
//x = x + delta;
blockExtension();
blockTranslation(x, y);
checkBlock();
if (busy == 0){
drawBlock();
}
else
{
clock = +1;
blockRotation(clock);
blockExtension();
blockTranslation(x, y);
drawBlock();
}
}
void checkForFullLine() { // --------------------- check if the line is full and must be deleted --------------
for (int i = 0; i < 4; i++){
for (int j = 45; j < 76; j += 3) {
if (TV.get_pixel(j, blockTmp[i][1]) >0){k++; }
}
if (k == 11) { // ------------------------------- line is full and must be deleted ----------------------
TV.draw_line(45, blockTmp[i][1], 78, blockTmp[i][1], 0);
TV.draw_line(45, blockTmp[i][1] + 1, 78, blockTmp[i][1] + 1, 0);
yLineTmp[yCounter] = blockTmp[i][1];
yLine[yCounter] = blockTmp[i][1];
yCounter++;
TV.tone(660, 60);
}
k = 0;
}
if (yLineTmp[yCounter - 1] < yLine[0]) { // ------------ not sure if it is < or > ------------------------
for (int i = 0; i < yCounter; i++) { // ------------- inversion --------------------------------------
yLine[i] = yLineTmp[yCounter - i - 1];
}
}
for (int i = 0; i < yCounter; i++){ // ----------- block translation to lower position ----------------
for (int y = yLine[i] - 2; y > 0; y = y - 2) {
for (int x = 45; x < 76; x += 3) {
colorOld = TV.get_pixel(x, y);
if (colorOld > 0) {
TV.draw_line(x, y , x + 3, y , 0);
TV.draw_line(x, y + 1, x + 3, y + 1, 0);
TV.draw_line(x, y + 2, x + 3, y + 2, colorOld);
TV.draw_line(x, y + 3, x + 3, y + 3, colorOld);
}
}
}
}
if (yCounter != 0) {
score = score + 2*int(pow(2, yCounter));
scoreTot = scoreTot + 2*int(pow(2, yCounter));
TV.tone(990, 60);
}
drawScore(score);
yCounter = 0;
}
//-----------------------------------------------------------------------------------------------
//--------------------- This is the main loop of the game ---------------------------------------
//-----------------------------------------------------------------------------------------------
void loop() {
processInputs();
if (noLoop < 1){ // --------------- to generate new tetraminos --------------------------------
blockN = blockNext;
if (noLoop == -1 ) { // -------------- only at the game beginning -------------------------
drawMenu();
while (button_1 == 0 && button_2 == 0 && button_3 == 0 && button_4 == 0 && button_5 == 0) {
randomSeed(millis());
blockN = int(random(6)) + 2;
processInputs();
}
}
drawGameScreen();
drawScore(score);
//blockNext = int(random(8));
blockNext = 1 + random(7);
blockDef(blockNext);
drawBlockNext();
blockDef(blockN);
x = 60; // --------------------- tetramino starting position -----------------
y = 5;
noLoop = 1;
}
if (button_2 == 1 || button_5 == 1){ // -------------- rotation -----------------
if (button_2 == 1){clock = -1;}
if (button_5 == 1){clock = 1;}
delBlock();
blockRotation(clock);
checkBlockRotation();
}
if (button_1 == 1 || button_3 == 1){ // ------- translation ----------------------
if (button_1 == 1){delta = 3;}
if (button_3 == 1){delta = -3;}
delBlock();
checkBlockTranslation();
}
time++;
if (time % fast == fast - 1 || button_4 == 1 ){ // --- tetramino falls every n frames, where n = "fast", or when button_4 is pressed ----------
y = y + 2;
delBlock();
replaceBlock();
}
}