VIDEOCONSOLA KLAX

Autores:

  • Guillermo J. Gª-Delgado Álvarez
  • Eva Pastor Abánades
  • Iván Rodríguez García

Idea del proyecto:

El videojuego que hemos desarrollado toma inspiración de “Klax de Atari 1989” y para el diseño de la máquina, que cumple el papel de consola, se ha usado como referencia la “Mesa TETRIS casera con LEDs RGB Direccionables de ELECTRONOOBS”.

Esta máquina se divide en cuatro elementos: la zona superior del tablero (donde caen los bloques de colores), la línea intermedia o línea de captura (que tiene un hueco que retiene al bloque que cae), una zona inferior del tablero donde se almacenan los bloques que deje caer el jugador donde él decida y, por último, la zona de los mandos (donde están los botones, el joystick y un regulador del brillo de las luces).

Para conseguir puntos, el jugador colocará dichos bloques intentando alinearlos en grupos de 3 o más; mientras que si no consigue soltar el bloque que ha capturado antes de que le llegue un nuevo bloque o no coloca bien el hueco para capturarlo, el jugador perderá puntos y la zona de captura parpadeará en rojo.

El “Game over” se producirá cuando los bloques se acumulen en la zona inferior del tablero y alcancen la línea de captura o cuando la puntuación baje a cero.

Materiales:

ARTÍCULOCANTIDADPRECIO/UNIDADPRECIO TOTALENLACE DE COMPRA
LED STRIP680,085,44https://es.aliexpress.com/item/2036819167.html?spm=a2g0o.productlist.0.0.2e0965c4pJStjE&algo_pvid=ec7f26fd-c0b3-413c-a530-17a1a0491b74&algo_expid=ec7f26fd-c0b3-413c-a530-17a1a0491b74-34&btsid=0b0a050b16105484575454056e7573&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
Botones60,060,36https://es.aliexpress.com/item/33046945784.html?spm=a2g0o.productlist.0.0.ee675499usGbIz&algo_pvid=35e428b1-3a41-465e-bd7f-5587105c82cd&algo_expid=35e428b1-3a41-465e-bd7f-5587105c82cd-17&btsid=2100bdd816105483518634863ef163&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
toggle switch12,232,23https://es.aliexpress.com/item/4000389806008.html?spm=a2g0o.productlist.0.0.20956813wUOuMr&algo_pvid=e0e3552e-1fda-490c-98fa-3c8c7b37e3d6&algo_expid=e0e3552e-1fda-490c-98fa-3c8c7b37e3d6-0&btsid=2100bb4a16105444320893520ec7a4&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
Joystick10,540,54https://es.aliexpress.com/item/32677559259.html?spm=a2g0o.productlist.0.0.4d9c75d8lED1Iq&algo_pvid=9b5b4d1b-2a0b-4cdd-b531-ab8b4e6215a2&algo_expid=9b5b4d1b-2a0b-4cdd-b531-ab8b4e6215a2-10&btsid=0b0a182b16105438414296329e5807&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
Potenciómetro11,671,67https://es.aliexpress.com/item/4000098635621.html?spm=a2g0o.productlist.0.0.44d06ec4S6yTFK&algo_pvid=79090242-9fea-4799-87df-7ffef775242f&algo_expid=79090242-9fea-4799-87df-7ffef775242f-12&btsid=2100bde716105485851118917e919a&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
Buzzer11,191,19https://es.aliexpress.com/item/32978143497.html?spm=a2g0o.productlist.0.0.3e6fa35dj7pQJx&algo_pvid=7cd032d6-b336-4831-805b-f79eea03de3f&algo_expid=7cd032d6-b336-4831-805b-f79eea03de3f-0&btsid=2100bdd516110882458307692eb465&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
Pila 9V15,115,11https://es.aliexpress.com/item/4001171825889.html?spm=a2g0o.productlist.0.0.38b843ddUgtv5s&algo_pvid=329e9ae7-7307-4f26-9989-3d465ce844ac&algo_expid=329e9ae7-7307-4f26-9989-3d465ce844ac-1&btsid=0b0a187916105486925232578ea5ae&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
Adaptador pila10,290,29https://es.aliexpress.com/item/1097854909.html?spm=a2g0o.productlist.0.0.340d63d1Z9Xc3z&algo_pvid=08e9611c-0871-4da4-9e7d-1e309870deef&algo_expid=08e9611c-0871-4da4-9e7d-1e309870deef-6&btsid=2100bdd816105487515391655ef152&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
Arduino 112,932,93https://es.aliexpress.com/item/32272099710.html?spm=a2g0o.productlist.0.0.158d62de2HDHDl&algo_pvid=bb70b5ab-5141-4280-9797-7d59b0e06c3e&algo_expid=bb70b5ab-5141-4280-9797-7d59b0e06c3e-0&btsid=0b0a050b16105486505215583e757d&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
TOTAL:18,57

Hardware de la máquina:

Todo esto sin contar con los materiales para la caja, que son extra dado que cumplen una función meramente estética.

La máquina cuenta con una zona de leds de 5 columnas y 12 filas que actúan como pantalla del juego, y una zona inferior destinada a la interacción del jugador a modo de mandos. Esta última cuenta con una tira de 8 leds en fila que indican la puntuación, 6 botones cuyas funciones se explicarán más adelante, un joystick y un potenciómetro que regula la intensidad de las luces. En el lateral de la caja se encuentra el agujero que permite conectar la máquina al ordenador y subir el código, y un toggle switch que permite el paso de corriente de una pila de 9V dentro de la caja, para así no tener que conectarlo al ordenador.

A continuación se mostrará una versión simplificada de Tinkercard:

En esta imagen se muestran los pines utilizados: 13 y 2 para los leds, 10-5 para los botones, el 4 para el buzzer y el A2 conectado al potenciómetro. Estos se han tanto a ground (los botones y el buzzer con sus respectivas resistencias), como a la conexión de 5v con ayuda de la motherboard. También podemos observar que las conexiones de la tira de leds de 5×12 se han unido de manera vertical, a pesar de tener un mayor gasto de cables, para facilitar la programación.

El diagrama mostrado anteriormente no consta de Joystick, por lo que este será explicado a continuación:

Se ha conectado el GND de este componente al Ground, y el Vcc a la energía. El SW se ha conectado al pin 11, el Vrx al pin A0 y el Vry al pin A1; pero no se han utilizado para este proyecto.

El toggle switch permite cortar o darle electricidad a nuestro proyecto cuando sea necesario con ayuda de una pila de 9V. Una parte va al positivo de la pila,  otra parte al Vin del arduino, y la parte ground va directamente al ground del arduino.

En resumen: los componentes que hemos usado se han conectado de la siguiente forma: pines 13 y 2 para los LEDS, 10-5 para los botones, 4 para el buzzer, A0 para el joystick y A2 para el potenciómetro. Además se conectó la pila al toggle switch para dar corriente a la videoconsola.

Funcionamiento de la máquina:

  • Para encender la máquina se accede al toggle switch, que abre la corriente de la pila de alimentación que está incorporada en el sistema.
  • Para mover el hueco que atrapa bloques manejado por el jugador se pueden pulsar tanto el joystick como los botones negros en la parte derecha inferior de la zona de comandos.
  • Si se desea aumentar la velocidad de caída de los bloques se presionará el botón azul.
  • Cuando se quiera atrapar un bloque que caiga por la zona superior de la pantalla, se deberá posicionar el hueco vacío de la línea blanca controlado por el jugador en la misma columna que el que se desee atrapar.
  • Una vez tenga un bloque atrapado podrá soltarlo pulsando el botón amarillo cuadrado.
  • Al pulsar el botón redondo blanco se pausará el juego y cuando se pulse el botón redondo amarillo se silenciará.
  • En la zona superior de los mandos, además de los botones, joystick, etc, también se encuentran una serie de leds alineados que representan la puntuación del jugador.
  • Si el jugador consigue alinear tres o más bloques del mismo color en cualquier dirección conseguirá 500 puntos. Por cada 1000 puntos conseguidos se encenderá una luz de la fila de leds que representan la puntuación.
  • Si el jugador no es capaz de atrapar un bloque se le restará 1000 puntos, la zona intermedia de la pantalla parpadeará en color rosa y se apagará un led de puntuación.
  • El jugador escuchará una música feliz si consigue puntos, y una música triste si los pierde o si pierde el juego.
  • El “game over” se produce cuando no quedan puntos o cuando no queda hueco en la zona de apilar bloques (la zona inferior de la pantalla).
  • Cuando se produzca el “game over” sonará la música triste y la pantalla entera se pintará de rosa con las letras “K.O.” en amarillo en la zona superior y un parpadeo en la zona inferior.

Problemas y conclusiones:

Por lo general, no han habido problemas más allá de descartar las primeras ideas por cuestiones de precios y tiempos de llegada de algunos componentes. Una vez estuvimos desarrollando la idea definitiva las fases más complejas fueron, por un lado la soldadura de los leds, que es algo muy delicado y a lo que se le tuvo que dedicar bastante tiempo, y por otro el código definitivo, cuya base se programó rápidamente pero muchas funcionalidades y su posterior refinamiento tomó mucho esfuerzo.

Más allá de esto se ha desarrollado la máquina sin contratiempos ni problemas graves de última hora, por lo que estamos muy contentos con el resultado obtenido. Pensamos que hemos creado una consola tan divertida como rústica, y con bastante personalidad.

Video demostración:

Código:

#include <Adafruit_NeoPixel.h>

//Configuración de los LEDs.
#define SCREEN_COLUMN 5
#define SCREEN_ROW 12
#define LEDS_COLUMN 8
#define SCREEN_NUM_LEDS (SCREEN_COLUMN*SCREEN_ROW)
#define SCREEN_PIN 13
#define LEDS_PIN 2
#define NUM_COLOR 8

Adafruit_NeoPixel screen_pix = Adafruit_NeoPixel(SCREEN_NUM_LEDS, SCREEN_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel leds_pix = Adafruit_NeoPixel(LEDS_COLUMN, LEDS_PIN, NEO_GRB + NEO_KHZ800);

struct Color{
  uint8_t r;
  uint8_t g;
  uint8_t b;
  
  Color(uint8_t red, uint8_t green, uint8_t blue){
    r = red;
    g = green;
    b = blue;
  }
  Color(){
    r = 0;
    g = 0;
    b = 0;
  }
};

Color colorRed = Color(255, 0, 0); //ROJO
Color colorOrange = Color(255, 69, 0); //NARANJA
Color colorYellow = Color(255, 255, 0); //AMARILLO
Color colorGreen = Color(0, 128, 0); //VERDE
Color colorCyan = Color(0, 255, 255); //AZUL CYAN
Color colorBlue = Color(0, 0, 255); //AZUL OSCURO
Color colorWhite = Color(255, 255, 255); // BLANCO
Color colorBlack = Color(0, 0, 0); // NEGRO

Color colores[NUM_COLOR] = {colorBlack, colorWhite, colorRed, colorOrange, colorYellow, colorGreen, colorCyan, colorBlue};

int brightness = 250;

//Matrices.
int SCREEN_COLORS[SCREEN_ROW][SCREEN_COLUMN];
int LEDS_COLORS[LEDS_COLUMN];

//Botones                   //Arreglar donde eva.
const int buttonPinMute = 5;      //Mute.
const int buttonPinPause = 6;     //Pausa.
const int buttonPinDrop = 7;      //Soltar pieza.
const int buttonPinCatch = 8;     //Coger pieza.
const int buttonPinLeft = 9;      //Izquierda.
const int buttonPinRight = 10;    //Derecha.

int buttonStateMute = 0;
int buttonStatePause = 0;
int buttonStateDrop = 0;
int buttonStateCatch = 0;
int buttonStateLeft = 0;
int buttonStateRight = 0;

bool mute_pressed = false;
bool pause_pressed = false;
bool drop_pressed = false;
bool catch_pressed = false;
bool left_pressed = false;
bool right_pressed = false;

const int analogInPin = A2; // Potenciómetro.
int sensorValue = 0;

//Sonido
long DO=523.25, RE=587.33,SOL=783.99, LA=880;
const int buzzer_pin = 4;

//Joystick
int xPin = A0; //Horizontal.
int yPin = A1; //Vertical.
int buttonPin = 11; //Botón de joystick.

int xPosition = 0;
int yPosition = 0;
int buttonState = 0;
int joy_delta = 250;

//Time.
const unsigned long MS_PER_UPDATE = 10;
unsigned long previous = 0;
unsigned long lag = 0;
unsigned long game_time = 0;

const unsigned long falling_delay = 1500;
const unsigned long generate_block_delay_min = 2000;
const unsigned long generate_block_delay_max = 4000;
int generate_delay = 0;

const int fastSpeed = 10;

const unsigned long time_tilting_blocks = 300;
const unsigned long time_erasing_blocks = time_tilting_blocks * 7;
const unsigned long time_failing = time_tilting_blocks * 5;
const unsigned long time_gameover = 8000;

//Gameplay
bool pause = false;
bool muted = false;
#define MAX_POINTS 8000 //Puntos máximos a representar.
#define POINTS_PER_BLOCK 500
#define POINTS_PER_FAIL -1000
int points = (int)((float)MAX_POINTS/2);

#define PLAYER_ROW 5
int player_pos = 2;
int player_color = 0;

bool falling_fast = false;
bool erasing_blocks = false;

/////////////////////////
// FUNCIONES GENERALES //
/////////////////////////

//Función que comprueba las entradas del sistema.
void input() {  
  //Comprobamos el mute.
  buttonStateMute = digitalRead(buttonPinMute);
  if(buttonStateMute == 0) mute_pressed = false;
  if(!mute_pressed && buttonStateMute!=0){
    mute_pressed = true;
    muted = !muted;
  }
  
  //Comprobamos la pausa.
  buttonStatePause = digitalRead(buttonPinPause);
  if(buttonStatePause == 0) pause_pressed = false;
  
  if(!pause_pressed && buttonStatePause!=0){
    pause_pressed = true;
    pause = !pause;
  }

  //Comprobamos el joystick.
  xPosition = analogRead(xPin);
  yPosition = analogRead(yPin);

  //Comprobamos los botones.
  buttonStateDrop = digitalRead(buttonPinDrop);
  if(buttonStateDrop == 0) drop_pressed = false;
  buttonStateCatch = digitalRead(buttonPinCatch);
  if(buttonStateCatch == 0) catch_pressed = false;
  buttonStateLeft = digitalRead(buttonPinLeft);
  if((buttonStateLeft == 0) && (xPosition < (500 + joy_delta))) left_pressed = false;
  buttonStateRight = digitalRead(buttonPinRight);
  if((buttonStateRight == 0) && (xPosition > (500 - joy_delta))) right_pressed = false;

  //Leemos los valores del potenciómetro y se actualiza el brillo de los leds.
  int newSensorValue = analogRead(analogInPin);
  if(sensorValue != newSensorValue){
    sensorValue = newSensorValue;
    
    //Hacemos un cambio de rango de potenciómetro a brillo de los leds.
    brightness = (int)(((float)sensorValue/1023) * 255);
    screen_pix.setBrightness(brightness);
    leds_pix.setBrightness(brightness);
  }
}

//Función que gestiona el funcionamiento de la máquina
// en función de las entradas introducidas.
void update_game(){
  
  //Se borran bloques si deben ser borrados.
  if(erasing_blocks) erase_blocks();

  //Comprobamos movimiento del jugador.
  player_update();
  
  //Comprobamos si puede caer rápido.
  do_fall_fast();
  
  //Calculamos a qué velocidad descienden los bloques.
  unsigned long fall_delay = falling_delay;
  if(falling_fast) fall_delay /= fastSpeed;

  //Cuando los bloques tengan que caer.
  if((game_time % fall_delay==0)){
    
    //Se comprueba si al caer algún bloque de la última fila es cogido por el jugador o resta puntuación.
    catching_block();
    
    falling_blocks();

    //Generamos un nuevo bloque en un rango de tiempo establecido.
    if(generate_delay<=0){
      generate_blocks();
      generate_delay = random(generate_block_delay_min, generate_block_delay_max);
    }
  }
  
  //Se rebaja el retraso del generador de bloques.
  if(generate_delay>0){
    if(falling_fast) generate_delay -= MS_PER_UPDATE*fastSpeed;
    else  generate_delay -= MS_PER_UPDATE;
  }

  //Se comprueba si se deja caer pieza si no se siguen eliminando bloques.
  if(!erasing_blocks) drop_block();
}

//Función que establece los valores de salida de los leds y los muestra.
void render() {
  //Renderizamos la pantalla de leds.
  for(int i = 0; i < SCREEN_ROW; i++){
    for(int j = 0; j < SCREEN_COLUMN; j++){
      Color ledColor = colores[SCREEN_COLORS[i][j]];
      int ledIndex = j * SCREEN_ROW + i;
      screen_pix.setPixelColor(ledIndex, ledColor.r, ledColor.g, ledColor.b); 
    }
  }
  update_player_line();
  screen_pix.show();

  //Renderizamos la fila de leds.
  int num_leds = (int)(((float)points / MAX_POINTS) * LEDS_COLUMN);
  for(int i = 0; i < LEDS_COLUMN; i++){
    Color ledColor = colores[LEDS_COLORS[i]];
    if(i <= num_leds) leds_pix.setPixelColor(i, ledColor.r, ledColor.g, ledColor.b);
    else      leds_pix.setPixelColor(i, 0, 0, 0);
  }
  leds_pix.show();
}

///////////////////////////
// FUNCIONES ADICIONALES //
///////////////////////////

//Función que actualiza la posición del jugador.
void player_update(){
  if(!left_pressed && (buttonStateLeft != 0 || (xPosition >= (500 + joy_delta)))){
    left_pressed = true;
    if(player_pos>0) player_pos--;
  }
  if(!right_pressed && (buttonStateRight != 0 || (xPosition <= (500 - joy_delta)))){
    right_pressed = true;
    if(player_pos<SCREEN_COLUMN-1) player_pos++;
  }
  update_player_line();
}

//Función que actualiza la línea del jugador, por donde realiza su movimiento.
void update_player_line(){
  for(int i = 0; i < SCREEN_COLUMN; i++){
    SCREEN_COLORS[PLAYER_ROW][i] = 1;
  }
  SCREEN_COLORS[PLAYER_ROW][player_pos] = player_color;
}

//Función que se encarga de generar bloques nuevos en el juego.
void generate_blocks(){
  int pos = random(SCREEN_COLUMN-1);
  int color = random(2, NUM_COLOR);
  SCREEN_COLORS[SCREEN_ROW-1][pos] = color;
}

//Función que provoca la caída de los bloques.
void falling_blocks(){
  for(int i = PLAYER_ROW+2; i < SCREEN_ROW; i++){
    for(int j = 0; j < SCREEN_COLUMN; j++){
      if(SCREEN_COLORS[i][j]!=0){
        SCREEN_COLORS[i-1][j] = SCREEN_COLORS[i][j];
        SCREEN_COLORS[i][j] = 0;
      }
    }
  }
}

//Función que activa la captura rápida de bloques.
void do_fall_fast(){
  if(!catch_pressed && buttonStateCatch != 0 && player_color == 0 && !falling_fast){
    bool canCatch = false;
    int i = PLAYER_ROW+1;
    while(i < SCREEN_ROW && !canCatch){
      if(SCREEN_COLORS[i][player_pos] != 0) canCatch = true;
      i++;
    }
    if(canCatch){
      catch_pressed = true;
      falling_fast = true;
    }
  }
}

//Función que captura un bloque o provoca pérdida de puntos al no cogerlo.
void catching_block(){  
  //Comprobamos pérdida de puntuación o captura de pieza.
  for(int i = 0; i < SCREEN_COLUMN; i++){
    if(SCREEN_COLORS[PLAYER_ROW+1][i] != 0){
      if(i==player_pos && SCREEN_COLORS[PLAYER_ROW][player_pos]==0){
        player_color = SCREEN_COLORS[PLAYER_ROW+1][i];
        update_player_line();
        SCREEN_COLORS[PLAYER_ROW+1][i] = 0;
      }else{
        
        //Perdida de puntuación.
        SCREEN_COLORS[PLAYER_ROW+1][i] = 0;
        update_points(POINTS_PER_FAIL);

        //Animación de fallo.
        Color ledColor = colores[0];
        int ledIndex = i * SCREEN_ROW + PLAYER_ROW;
        screen_pix.setPixelColor(ledIndex, ledColor.r, ledColor.g, ledColor.b);
        screen_pix.show();

        unsigned long time_lifted = 0;
        bool red = true;
        int note = 0;
        while(time_lifted < time_failing){
          for(int j = 0; j < SCREEN_COLUMN; j++){
            int ledIndex = j * SCREEN_ROW + PLAYER_ROW;
            if(red) screen_pix.setPixelColor(ledIndex, 255, 50, 50);
            else{
              Color ledColor = colores[SCREEN_COLORS[PLAYER_ROW][j]];
              screen_pix.setPixelColor(ledIndex, ledColor.r, ledColor.g, ledColor.b);
            }
          }
          screen_pix.show();
          time_lifted += time_tilting_blocks;
          delay(time_tilting_blocks);
          previous += time_tilting_blocks;
          red = !red;
          //Musica de pérdida de puntos.
          die_music(note++);
        }
        //Restablecemos el sonido
        noTone(buzzer_pin);
        for(int j = 0; j < SCREEN_COLUMN; j++){
          Color ledColor = colores[SCREEN_COLORS[PLAYER_ROW][j]];
          screen_pix.setPixelColor(ledIndex, ledColor.r, ledColor.g, ledColor.b);
        }
        screen_pix.show();
      }
      falling_fast = false;
    }
  }
}

//Función que se encarga del comportamiento de cuando un jugador quiere soltar un bloque.
void drop_block(){
  if(!drop_pressed && buttonStateDrop != 0 && player_color != 0){
    bool can_drop = false;
    int i = 0;
    while(i < PLAYER_ROW && !can_drop){
      if(SCREEN_COLORS[i][player_pos] == 0) can_drop = true;
      if(!can_drop) i++;
    }
    if(can_drop){
      drop_pressed = true;
      erasing_blocks = true;
      SCREEN_COLORS[i][player_pos] = player_color;
      player_color = 0;
      update_player_line();
    }
  }
}

//Función que se encarga de eliminar bloques cuando forman filas de tres en tres.
void erase_blocks(){
    bool can_erase = false;
    int i = 0;
    while(i < PLAYER_ROW && !can_erase){
      int j = 0;
      while(j < SCREEN_COLUMN && !can_erase){
        if(check_lines(i, j)) can_erase = true;
        j++;
      }
      i++;
    }
    
    if(!can_erase){
      erasing_blocks = false;
      check_is_full();
    }
}

//Función que comprueba si un bloque en concreto está formando una línea de 3.
bool check_lines(int row, int column){
  if(SCREEN_COLORS[row][column]!=0){
    int count_north = 0;
    int count_south = 0;
    int count_east = 0;
    int count_west = 0;
    int count_north_west = 0;
    int count_south_east = 0;
    int count_south_west = 0;
    int count_north_east = 0;
    int i,j;
   
    //Norte.
    i = row + 1;
    while(i < PLAYER_ROW && SCREEN_COLORS[i][column] == SCREEN_COLORS[row][column]){
      count_north++;
      i++;
    }
    
    //Sur.
    i = row - 1;
    while(i >= 0 && SCREEN_COLORS[i][column] == SCREEN_COLORS[row][column]){
      count_south++;
      i--;
    }
    
    if(count_north + count_south >= 2){
      do_erase_blocks(row - count_south, column, count_north + count_south, 0);
    }else{
    
      //Oeste.
      j = column - 1;
      while(j >= 0 && SCREEN_COLORS[row][j] == SCREEN_COLORS[row][column]){
        count_west++;
        j--;
      }
      
      //Este.
      j = column + 1;
      while(j < SCREEN_COLUMN && SCREEN_COLORS[row][j] == SCREEN_COLORS[row][column]){
        count_east++;
        j++;
      }
    
      if(count_west + count_east >= 2){
        do_erase_blocks(row, column - count_west, 0, count_west + count_east);
      }else{
        
        //Noroeste.
        i = row + 1;
        j = column - 1;
        while(i < PLAYER_ROW && j >= 0 && SCREEN_COLORS[i][j] == SCREEN_COLORS[row][column]){
          count_north_west++;
          i++;
          j--;
        }
      
        //Sureste.
        i = row - 1;
        j = column + 1;
        while(i >= 0 && j < SCREEN_COLUMN && SCREEN_COLORS[i][j] == SCREEN_COLORS[row][column]){
          count_south_east++;
          i--;
          j++;
        }
        
        if(count_north_west + count_south_east >= 2){
          do_erase_blocks(row + count_north_west, column - count_north_west, -(count_north_west + count_south_east), count_north_west + count_south_east);
        }else{
          
          //Suroeste.
          i = row - 1;
          j = column - 1;
          while(i >= 0 && j >= 0 && SCREEN_COLORS[i][j] == SCREEN_COLORS[row][column]){
            count_south_west++;
            i--;
            j--;
          }
        
          //Noreste.
          i = row + 1;
          j = column + 1;
          while(i < PLAYER_ROW && j < SCREEN_COLUMN && SCREEN_COLORS[i][j] == SCREEN_COLORS[row][column]){
            count_north_east++;
            i++;
            j++;
          }
          
          if(count_south_west + count_north_east >= 2){
            do_erase_blocks(row - count_south_west, column - count_south_west, count_south_west + count_north_east, count_south_west + count_north_east);
          }else{
            return false;
          }
        }
      }
    }
    return true;
  }else{
    return false;
  }
}

//Función que se encarga de eliminar los bloques y hacer una animación.
void do_erase_blocks(int row, int column, int rowMove, int columnMove){
   if(rowMove!=0 || columnMove!=0){
      unsigned long time_lifted = 0;
      bool blanck = true;
      while(time_lifted < time_erasing_blocks){
        int i = 0;
        int j = 0;
        int note = 0;
        while(i <= abs(rowMove) && j <= columnMove){
          int myRow = row + i;
          if(rowMove<0) myRow = row - i;
          int myColumn = column + j;

          //Animación de parpadeo al eliminar bloques.
          Color ledColor;
          if(blanck)  ledColor = colores[1];
          else        ledColor = colores[SCREEN_COLORS[myRow][myColumn]];
          int ledIndex = myColumn * SCREEN_ROW + myRow;
          screen_pix.setPixelColor(ledIndex, ledColor.r, ledColor.g, ledColor.b);
          
          if(rowMove != 0) i++;
          if(columnMove != 0) j++;
        }
        points_music(note++);
        screen_pix.show();
        time_lifted += time_tilting_blocks;
        delay(time_tilting_blocks);
        previous += time_tilting_blocks;
        blanck = !blanck;
      }
      noTone(buzzer_pin);

      //Eliminamos los bloques.
      int i = 0;
      int j = 0;
      while(i <= abs(rowMove) && j <= columnMove){
        int myRow = row + i;
        if(rowMove<0) myRow = row - i;
        int myColumn = column + j;
        SCREEN_COLORS[myRow][myColumn] = 0;
        if(rowMove != 0) i++;
        if(columnMove != 0) j++;
      }
      
      //Obtenemos el número de bloques que dan  puntos.
      int num_blocks_points = abs(rowMove);
      if(num_blocks_points < columnMove) num_blocks_points = columnMove;
      num_blocks_points--;
      
      //Obtener puntuación.
      update_points(POINTS_PER_BLOCK * num_blocks_points);
      
      //Caída de bloques.
      for(int i = 0; i < PLAYER_ROW; i++){
         for(int j = 0; j < SCREEN_COLUMN; j++){
            if(SCREEN_COLORS[i][j]==0){
               bool encontrado = false;
               int k = i + 1;
               while(k < PLAYER_ROW && !encontrado){
                  if(SCREEN_COLORS[k][j]!=0){
                    encontrado = true;
                    SCREEN_COLORS[i][j] = SCREEN_COLORS[k][j];
                    SCREEN_COLORS[k][j] = 0;
                  }
                  k++;
               }
            }
         }
      }
   }
}

//Función que comprueba si hay hueco disponible para soltar un bloque,
//si no lo hay, fin de la partida.
void check_is_full(){
  bool hay_hueco = false;
  int i = 0;
  while(i < SCREEN_COLUMN && !hay_hueco){
    if(SCREEN_COLORS[PLAYER_ROW-1][i] == 0) hay_hueco = true;
    i++;
  }
  if(!hay_hueco) gameover();
}

//Función que establece el funcionamiento del fin de la partida.
void gameover(){
  //Animación de perder partida.
  unsigned long time_lifted = 0;
  bool red = true;

  //Vaciamos la pantalla de arriba.
  for(int i = PLAYER_ROW + 1; i < SCREEN_ROW; i++){
    for(int j = 0; j < SCREEN_COLUMN; j++){
      int ledIndex = j * SCREEN_ROW + i;
      screen_pix.setPixelColor(ledIndex, 255, 50, 50);
      screen_pix.show();
    }
  }

  //Escribimos un KO.
  //Letra K.
  screen_pix.setPixelColor(SCREEN_ROW - 2, 255, 255, 0);
  screen_pix.setPixelColor(SCREEN_ROW - 3, 255, 255, 0);
  screen_pix.setPixelColor(SCREEN_ROW - 4, 255, 255, 0);
  screen_pix.setPixelColor(SCREEN_ROW + SCREEN_ROW - 3, 255, 255, 0);
  screen_pix.setPixelColor(2 * SCREEN_ROW + SCREEN_ROW - 2, 255, 255, 0);
  screen_pix.setPixelColor(2 * SCREEN_ROW + SCREEN_ROW - 4, 255, 255, 0);
  
  //Letra O.
  screen_pix.setPixelColor((SCREEN_COLUMN - 1) * SCREEN_ROW + PLAYER_ROW + 1, 255, 255, 0);
  screen_pix.setPixelColor((SCREEN_COLUMN - 2) * SCREEN_ROW + PLAYER_ROW + 1, 255, 255, 0);
  screen_pix.setPixelColor((SCREEN_COLUMN - 3) * SCREEN_ROW + PLAYER_ROW + 1, 255, 255, 0);
  screen_pix.setPixelColor((SCREEN_COLUMN - 1) * SCREEN_ROW + PLAYER_ROW + 2, 255, 255, 0);
  screen_pix.setPixelColor((SCREEN_COLUMN - 3) * SCREEN_ROW + PLAYER_ROW + 2, 255, 255, 0);
  screen_pix.setPixelColor((SCREEN_COLUMN - 1) * SCREEN_ROW + PLAYER_ROW + 3, 255, 255, 0);
  screen_pix.setPixelColor((SCREEN_COLUMN - 2) * SCREEN_ROW + PLAYER_ROW + 3, 255, 255, 0);
  screen_pix.setPixelColor((SCREEN_COLUMN - 3) * SCREEN_ROW + PLAYER_ROW + 3, 255, 255, 0);
  screen_pix.show();
  
  //Hacemos que la de abajo parpadee.
  int note = 0;
  while(time_lifted < time_gameover){
    for(int i = 0; i < PLAYER_ROW; i++){
      for(int j = 0; j < SCREEN_COLUMN; j++){
        int ledIndex = j * SCREEN_ROW + i;
        if(red) screen_pix.setPixelColor(ledIndex, 255, 50, 50);
        else    screen_pix.setPixelColor(ledIndex, 0, 0, 0);
      }
      //Musica de pérdida de puntos.
      die_music(note++);
    }
    noTone(buzzer_pin);
    time_lifted += time_tilting_blocks;
    screen_pix.show();
    delay(time_tilting_blocks);
    previous += time_tilting_blocks;
    red = !red;
  }
  //Restablecemos la partida.
  reset();
}

//Función que reinicia la partida para volver a jugar.
void reset(){
  
  //Restablecemos las variables.
  previous = millis();
  lag = 0;
  game_time = 0;
  generate_delay = 0;
  points = (int)((float)MAX_POINTS/2);
  player_pos = 2;
  player_color = 0;
  update_player_line();
  falling_fast = false;
  erasing_blocks = false;

  //Vaciamos la pantalla de arriba a abajo.
  for(int i = 0; i < SCREEN_ROW; i++){
    for(int j = 0; j < SCREEN_COLUMN; j++){
      SCREEN_COLORS[i][j] = 0;
    }
  }
  //Definimos los colores de la barra de puntuación.
  LEDS_COLORS[0] = 2;
  LEDS_COLORS[1] = 4;
  for(int i = 2; i < LEDS_COLUMN; i++){
    LEDS_COLORS[1] = 5;
  }
  render();
}

void update_points(int newPoints){
  //Calculamos cuantos leds deberán encenderse.
  points += newPoints;
  if(points<0) gameover();
}

//Sonido cuando pierde puntos o gameover.
void die_music(int note){
  if(!muted){
    switch(note%3){
      case 0:
      tone(buzzer_pin,DO);
      break;
      case 1:
      tone(buzzer_pin,RE);
      break;
      case 2:
      tone(buzzer_pin,DO);
      break;
      default:
      noTone(buzzer_pin);
      break;
    }
  }
}

//Sonido al ganar puntos.
void points_music(int note){
  if(!muted){
    switch(note){
      case 0:
        tone(buzzer_pin,SOL);
      break;
      case 1:
        tone(buzzer_pin,LA);
      break;
      default:
        noTone(buzzer_pin);
      break;
    }
  }
}
/////////////////////////////
//  FUNCIONES PRINCIPALES  //
/////////////////////////////

//Inicialización del videojuego.
void setup() {
  // Inicializar las comunicaciones en serie a 9600 bps:
  Serial.begin(9600);

  pinMode(xPin, INPUT);
  pinMode(yPin, INPUT);

  //Activar resistencia pull-up en el pin pulsador
  pinMode(buttonPin, INPUT_PULLUP);

  pinMode(buttonPinMute, INPUT);
  pinMode(buttonPinPause, INPUT);
  pinMode(buttonPinDrop, INPUT);
  pinMode(buttonPinCatch, INPUT);
  pinMode(buttonPinLeft, INPUT);
  pinMode(buttonPinRight, INPUT);
  pinMode(buzzer_pin, OUTPUT);

  screen_pix.begin();
  screen_pix.setBrightness(brightness);
  leds_pix.begin();
  leds_pix.setBrightness(brightness);

  //Inicializamos los leds.
  for(int i = 0; i < SCREEN_ROW; i++){
    for(int j = 0; j < SCREEN_COLUMN; j++){
      SCREEN_COLORS[i][j] = 0;
    }
  }

  //Definimos los colores de la barra de puntuación.
  LEDS_COLORS[0] = 2;
  LEDS_COLORS[1] = 4;
  for(int i = 2; i < LEDS_COLUMN; i++){
    LEDS_COLORS[i] = 5;
  }

  //Actualizamos la barra.  
  update_points(0);
  
  //Semilla aleatoria para valores aleatorios.
  randomSeed(sensorValue);
}

void loop() {
  //Patrón gameloop
  unsigned long current = millis();
  unsigned long elapsed = current - previous;
  previous = current;
  
  //Se comprueban todas las entradas del sistema.
  input();

  if(!pause){
    lag += elapsed;
    
    //Estado del juego, se actualiza según el lag.
    while (lag > MS_PER_UPDATE) {
      game_time += MS_PER_UPDATE;
      lag -= MS_PER_UPDATE;
      update_game();
    }
  }
  
  //Se establece el color de los leds.
  render();
}

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *