sábado, 27 de diciembre de 2014

Juego Snake en C++

Buenas a todos en este post les quiero compartir el código para implementar un juego de consola de los clásicos más grandes, “El Snake”. El código esta implementado en C++, es un poco viejo dado a que la mayoría de la implementación lo hice mientras cursaba la universidad, pero me tome el tiempo para hacerle algunas mejoras y cambios menores con el fin de compartirlo y que pueda servirles de ejemplo para proyectos similares. El juego solo tiene los aspectos básicos de la lógica de snake, cuenta con dos escenarios distintos uno con y otro sin paredes, se maneja con las flechas de dirección del teclado y el objetivo para ganar el juego es alcanzar la puntuación máxima configurada que por default es 500 pts. En la siguiente imagen podemos observar cómo se vería el juego en ejecución.


Básicamente la estructura del juego se basa en  dos archivos de cabecera y cinco clases (Nodo, Snake, Plataforma, Juego e Interfaz). La clase Snake tiene una relación de composición con Nodo mientras que la Juego tiene también una composición con Snake y Plataforma. Por último la interfaz se relaciona por agregación con la clase Juego. En el siguiente diagrama podemos observar mejor la relación de las clases.


Ahora veamos la implementación de los archivos cabecera, el primero "UtilidadezInterfaz.h" ya lo he utilizado antes en el ejemplo de Fundamentos Juegos en Consola C++ donde explique que implementa algunas funciones básicas como cambios de color de consola, posicionar el cursor dadas las coordenadas entre otras. Este es el código del encabezado.

////////////////////////////////////////////
// Definición Bibliotecas y definiciones
// q' utilizaremos en el código
///////////////////////////////////////////
#include <windows.h>
#include <conio.h>
#include <stdio .h=>

//Definiciones de teclas
#define ARR 72//Valor ASCII para tecla flecha arriba.
#define ABJ 80//Valor ASCII para tecla flecha abajo.
#define DER 77//Valor ASCII para tecla flecha Derecha.
#define IZQ 75//Valor ASCII para tecla flecha Izquierda.
#define ENT 13//Valor ASCII para tecla Enter.
#define ESC 27//Valor ASCII para tecla Esc.

//Definiciones de configuración pantalla Salir
#define LIN_HOR_SALIR      205//Lineas Horizontales
#define LIN_VER_SALIR      186//Lineas Vericales
#define ESQ_SUP_DER_SALIR  201//Esquina superior Derecha
#define ESQ_SUP_IZQ_SALIR  200//Esquina superior Izquierda
#define ESQ_INF_DER_SALIR  187//Esquina inferior Derecha
#define ESQ_INF_IZQ_SALIR  188//Esquina inferior Izquierda
#define COLOR_CAJA_SALIR   0x0E//Color de la caja de la pantalla salir.
#define OPC_SELEC_SALIR    0xDE//Color de la opción seleccionada en la pantalla salir.

////////////////////////////////////////////
//   Clase UtilidadesInterfaz
//Donde se implementara la lógica
//necesaria para crear una interfaz con flechas.
///////////////////////////////////////////
 class UtilidadesInterfaz{
public:
 //Funcion para capturar la tacla digitada omitiendo el el Enter.
 static int GetKey() {
  int Tecla=getch();
  if(Tecla==0xE0 || Tecla==0)
   Tecla=getch();
  return Tecla;
 }

  //Método  para Establecer los Colores recibe un numero de 2 digitos
 //el primer digito estabece el color del background de la consola.
 //el segundo numero establece el color de la letra de la consola.
 static void setColor(unsigned short color){
  //Funcion para cambiar el color de la consola
  SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),color);
 }

 //Método  que me posiciona el foco de cursor de la consola dadas 2 coordenadas.
 static void gotoxy(int x,int y){
  //Variable Coordenada que guarda las coordenadas donde voy a imprimir.
  COORD conCord;
  conCord.X=x;
  conCord.Y=y;
  //Método  posiciona el foco de cursor de la consola según  coordenadas especificadas.
  SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),conCord);
 }

 //Método  para cambiar propiedades del cursor.
 static void ocultaCursor(){
  //Variable para controlar atributos del cursor;
  CONSOLE_CURSOR_INFO myCursor;
  myCursor.dwSize = 25;
  myCursor.bVisible = FALSE;
  //Funcion para cambiar propiedades del cursor de la consola.
  SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&myCursor);
 }
//Función que confirma la desición del Usuario de salir del programa.
 static bool Salir(){
  system("CLS");
  system("color 0F");
  setColor(COLOR_CAJA_SALIR);
  //Dibujo las lineas horizontales de la caja.
  for(int i=19;i<59;i++){
   gotoxy(i,15);printf("%c",LIN_HOR_SALIR);
   gotoxy(i,5);printf("%c",LIN_HOR_SALIR);
  }

  //Dibujo las lineas verticales de la caja.
  for(int i=6;i<15;i++){
   gotoxy(18,i);printf("%c",LIN_VER_SALIR);
   gotoxy(59,i);printf("%c",LIN_VER_SALIR);
  }

  //Dibujo las esquinas de la caja
  gotoxy(18,5);printf("%c",ESQ_SUP_DER_SALIR);
  gotoxy(18,15);printf("%c",ESQ_SUP_IZQ_SALIR);
  gotoxy(59,5);printf("%c",ESQ_INF_DER_SALIR);
  gotoxy(59,15);printf("%c",ESQ_INF_IZQ_SALIR);

  //Dibujo el titulo de la caja.
  gotoxy(19,6); 
  printf("                                        \n");
  gotoxy(19,7); 
  printf(" Desea salir por completo del programa? \n");
  gotoxy(19,8); 
  printf("                                        \n");

  //Dibujo las opciones a esocger en la caja.
  int tecla = 0;//Tecla presionada, inicializada en 0 para primer caso.
  bool Opcion =true;//Variable que almacena el número de opción en el q se encuentra el Usuario.
  while(true){
   gotoxy(32,12);
   //Dibujamos las opciones a seleccionar
   setColor(COLOR_CAJA_SALIR);
   if(Opcion)
    setColor(OPC_SELEC_SALIR);
   printf("SI");
   gotoxy(42,12);
   setColor(COLOR_CAJA_SALIR);
   if(!Opcion)
    setColor(OPC_SELEC_SALIR);
   printf("NO");
   tecla=GetKey();//Leemos la tecla digitada.
   //En caso que la tecla digitada sea alguna de las aceptadas como acciones en el menú
   if(tecla==DER||tecla==IZQ||tecla==ENT){
    switch(tecla){
     case DER:Opcion=false;break;
     case IZQ:Opcion=true;break;
     case ENT:system("CLS");system("color 0F");return Opcion;
    }
   }
   Sleep(100);//Dormimos el proceso por 100 milisegundos.(Recomendable para liberar procesador)
  }
 }

 //Método para dibujar la portada inicial del Programa.
 static void Portada(){
 //Dibujo las lineas horizontales de la caja para la portada
 for(int i=2;i<78;i++){
  if(i>9 && i<69){
   setColor(0x0A);
   gotoxy(i,2);printf("%c",31);
   gotoxy(i,21);printf("%c",30);
  }else{
   setColor(0x0D);
   gotoxy(i,2);printf("%c",205);
   gotoxy(i,21);printf("%c",205);  
  }
 }

 //Dibujo las lineas verticales de la caja para la portada
 for(int i=3;i<22;i++){
  if(i>4&&i<18){
   setColor(0x0A);
   gotoxy(2,i);printf("%c",16);
   gotoxy(77,i);printf("%c",17);
  }
  else{
   setColor(0x0D);
   gotoxy(2,i);printf("%c",186);
   gotoxy(77,i);printf("%c",186);
  }
 }

 //Dibujo las esquinas de la caja para la portada
 gotoxy(2,2);printf("%c",201);
 gotoxy(2,21);printf("%c",200);
 gotoxy(77,2);printf("%c",187);
 gotoxy(77,21);printf("%c",188);
 //Dibujo creditos de la Portada.
 setColor(0x0E);
 gotoxy(27,8);printf("Mover Objetos con Flechas C++");
 gotoxy(25,12);printf("Programador: Greivin Chaves R.");
 gotoxy(25,18);printf("Derechos Reservados. 26/11/2013");
 setColor(0x0F);
 printf("\n");
 gotoxy(20,23);system("PAUSE");
}

};

El segundo archivo de encabezado “Defines.h” define algunas variables globales de configuración del juego, como son los caracteres ASCII a utilizar para dibujar el snake o las paredes, la puntuación máxima del juego, el tamaño del área de juego entre otras.

#ifndef DEFINES_H_INCLUDED
#define DEFINES_H_INCLUDED

//Definiciones de texturas globales del juego (Basado en caracteres Ascii).
#define piel 176        //Textura de piel del Snake
#define comida 2        //Carácter a mostrar como comida
#define espacio 0       //Carácter de espacio vacío.
#define pared 219       //Textura de la pared del área de juego.
#define limY 60         //Tamaño o limite en Y del área de juego
#define limX 20         //Tamaño o limite en X del área de juego
#define maxPoints 500   //Puntación máxima para ganar el juego.

#endif // DEFINES_H_INCLUDED

Ahora de las clases veamos primero la clase Nodo, esta permite guardar dos números enteros un “X” y “Y” que serían las coordenadas de cada sección que conforma el snake. Estos nodos son los que se agregan en caso que la culebra coma y tenga que crecer. Este es el código:

#include <iostream>
#include <sstream>
using namespace std;

////////////////////////////////////////////////////////////////////////////////////////////////
//                              Clase Nodo
//      Define cada uno de los nodos o secciones que conforman la snake
//      y guardan su ubicación dentro del área de juego
///////////////////////////////////////////////////////////////////////////////////////////////

class Nodo
{
private:
    unsigned int x,y; //Coordenadas para conocer la posición dentro del área de juego.
public:
    Nodo()
    {
        y=x=0;
    }

    Nodo(unsigned int _x,unsigned int _y)
    {
        y=_y;
        x=_x;
    }

    unsigned int getX()
    {
        return x;
    }

    unsigned int getY()
    {
        return y;
    }

    void setX(unsigned int _x)
    {
        x=_x;
    }

    void setY(unsigned int _y)
    {
        y=_y;
    }

    string toString()
    {
        stringstream s;
        s<<"X: "<<x<<" Y: "<<y<<endl;
        return s.str();
    }

    ~Nodo() {}
};

La clase Snake la cual esta implementada como una lista de nodos que van a conformar cada sección de la culebra controla los aspectos básicos como crecer (en el caso que coma), caminar, entre otras. El código de la clase es el siguiente:


#include <list>
#include "Nodo.cpp"
#define limX 60
#define limY 20

////////////////////////////////////////////////////////////////////////////////////////////////
//                              Clase Snake
//                  Define la snake, y controla cada uno de los nodos que la conforman.
///////////////////////////////////////////////////////////////////////////////////////////////


class Snake
{
private:
    list<Nodo> *snake;

    list<Nodo>::iterator it;

    /*
        Crea un nuevo Nodo basado en la dirección que lleva la snake y las posiciones del primer nodo o cabeza.
         El secreto de este método es posicionar los nuevos nodos al inicio de la culebra y no al final, para
         facilitar el efecto de que la culebra avanza.
    */
     Nodo * nuevoNodo(unsigned int opc){
         int x=snake->front().getX();
         int y=snake->front().getY();
         //OPC INDICA HACIA DONDE SE ENCUENTRA CAMINANDO
        //EL SNAKE PARA ASI AGREGAR LA COLA
        //case 1-> Arriba
        //case 2-> Abajo
        //case 3->Derecha
        //case 4->Izqierda
        switch(opc)
        {
        case 1:
        {
            y--;
            break;
        }
        case 2:
        {
            y++;
            break;
        }
        case 3:
        {
            x++;
            break;
        }
        case 4:
        {
            x--;
            break;
        }
        }
        if(x>=limX)
            x=0;
        if(x<0)
            x=limX-1;
        if(y>=limY)
            y=0;
        if(y<0)
            y=limY-1;
         Nodo * nuevo=new Nodo(x,y);
        return nuevo;
    }
public:

    Snake()
    {
        snake=new list<Nodo>();
    }

    //Método para inicializar la snake.
    void Inicia(unsigned int x,unsigned int y)
    {
        snake->clear();
        //Inicializamos la Cabeza
        snake->push_back(*new Nodo(x,y));
        //Agregamos la Cola para iniciar el Juego
        Crece(3);
        Crece(3);
    }

   //Método para que la snake crezca, el mismo llama al método privado nuevoNodo.
   //Para efectos de visualización por cada crecimiento se agregan dos nodos.
    void Crece(unsigned int opc)
    {
        snake->push_front(*nuevoNodo(opc));
        snake->push_front(*nuevoNodo(opc));
    }

    //Método para que la snake camine
    //(Se basa en eliminar el ultimo nodo y agregar uno al inicio, creando así un efecto de movimiento.)
    void Camina(int opc)
    {
        snake->pop_back();
        snake->push_front(*nuevoNodo(opc));
    }

    //Imprimir la snake.
    string toString()
    {
        stringstream r;
        for (it=snake->begin(); it!=snake->end(); it++)
            r<<(*it).toString();
        return r.str();
    }

    //Destructor (Importarnte liberar memoria de todo el snake)
    ~Snake()
    {
        snake->clear();
        delete snake;
    }

    //Método para obtener el contenido de donde se encuentra el iterador.
    Nodo Contenido(){
        return (*it);
    }

    //Cabeza de la snake
    void Inicio(){
        it=snake->begin();
    }

    //Cola de la Snake
    bool Final(){
        return it!=snake->end();
    }

    //Control del avance del iterador que recorre la snake.
    void Avanzo(){
        it++;
    }

};

La clase plataforma la cual es el área de juego donde se va a mover la culebra y donde aparecerá la comida de la misma está definida como una matriz de caracteres del tamaño definido en “Defines.h”. Existen dos tipos de plataformas una con paredes y otra sin paredes. Este es el código de la clase:

#include <iostream>
#include <sstream>
#include "../Defines.h"

using namespace std;

////////////////////////////////////////////////////////////////////////////////////////////////
//                              Clase Plataforma
//                     Define el área del juego donde puede moverse la snake
//                      (En este caso es manejado mediante una matriz de caracteres)
///////////////////////////////////////////////////////////////////////////////////////////////

class Plataforma
{
private:
    char **matriz;
public:

    Plataforma()
    {
        matriz=NULL;
    }

    //Método para generar el área de juego, según los límites de X y Y establecidos en  Defines.h
    void Genera()
    {
        matriz=new char*[limX];
        for (int i=0; i<limX; i++)
            matriz[i]=new char[limY];
    }

    //Método para presentar el primer escenario de juego. (Campo sin paredes en los bordes)
    void Escenario0()
    {
        for (int i=0; i<limX; i++)
            for (int j=0; j<limY; j++)
                if(matriz[i][j]!=(char)comida)
                    matriz[i][j]=' ';
    }

    //Método para presentar el segundo escenario de juego. (Campo con paredes en los bordes)
    void Escenario1()
    {
        for (int i=0; i<limX; i++)
        {
            for (int j=0; j<limY; j++)
            {
                if(matriz[i][j]!=(char)comida)
                {
                    if(j==0||j==limY-1)
                        matriz[i][j]=pared;
                    else if(i==0||i==limX-1)
                        matriz[i][j]=pared;
                    else
                        matriz[i][j]=' ';
                }
            }
        }
    }

     //Método para cambiar un sector del área de juego, según coordenadas  X y Y
     //Cambia el valor del area de juego por un nuevo caracter.
    bool setSector(int x,int y,int caracter)
    {
        bool retorno=false;
        if(getSector(x,y)==(char)piel||getSector(x,y)==(char)pared)
            throw 1;
        else
        {
            if(matriz[x][y]==char(comida))
                retorno=true;
            matriz[x][y]=caracter;
            return retorno;
        }
    }

    //Método para obtener un sector del área de juego, según coordenadas  X y Y
    char getSector(int x,int y)
    {
        return matriz[x][y];
    }

    //Método para limpiar todos los sectores del área de juego.
    void Limpia()
    {
        for (int i=0; i<limX; i++)
            delete matriz[i];
    }

    //Impime el encabezado del área de juego.
    void Encabezado(stringstream &s)
    {
        s<<(char)218;
        for (int i=0; i<limY; i++)
            s<<(char)196;
        s<<(char)191<<endl;
    }

    //Impime el pie del área de juego.
    void Pie(stringstream &s)
    {
        s<<(char)192;
        for (int i=0; i<limY; i++)
            s<<(char)196;
        s<<(char)217<<endl;
    }

    //Impime el área de juego.
    string toString()
    {
        stringstream s;
        Encabezado(s);
        for (int i=0; i<limX; i++)
        {
            s<<(char)179;
            for (int j=0; j<limY; j++)
            {
                s<<matriz[i][j];
            }
            s<<(char)179<<endl;

        }
        Pie(s);
        return s.str();
    }

    //Destructor (Importante liberar memoria del objeto)
    ~Plataforma()
    {
        if(matriz)
        {
            Limpia();
            delete []matriz;
        }
    }
};


La clase juego es la que implementa todas las reglas del juego y lógica principal, permite la interacción entre la plataforma y la snake. Contiene validaciones para determinar si se ganó el juego llegando al máximo puntaje o si se perdió al chocar contra la misma snake o con algún obstáculo. Este es el código de la clase:

#include <Windows.h>
#include <conio.h>
#include "Snake.cpp"
#include "../AreaJuego/Plataforma.cpp"
#include "../UtilidadesInterfaz.h"
#define ut UtilidadesInterfaz
#include <stdlib.h>

//Variable camina sentido del Snake
//1-> Arriba
//2-> Abajo
//3->Derecha
//4->Izqierda
unsigned int camina;
bool pauseGame;
bool gameOver;

// Hilo de proceso que procesa las teclas que son digitadas durante la ejecución del juego.
DWORD WINAPI Tecla(LPVOID iValue)
{
    int Tecla;
    pauseGame=false;
    while(true)
    {
        if(gameOver)
            return 0;
        Tecla=getch();
        if(Tecla==0xE0 || Tecla==0)
            Tecla=getch();
        switch(Tecla)
        {
        case 72:
        {
            if(camina!=2)
                camina=1;
            pauseGame=false;
            break;
        }
        case 80:
        {
            if(camina!=1)
                camina=2;
            pauseGame=false;
            break;
        }
        case 75:
        {
            if(camina!=3)
                camina=4;
            pauseGame=false;
            break;
        }
        case 77:
        {
            if(camina!=4)
                camina=3;
            pauseGame=false;
            break;
        }
        case 112:
        {
            pauseGame=true;
            break;
        }
        case 27:
        {
            pauseGame=false;
            gameOver=true;
            return 0;
            break;
        }
        }
    };
    return 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////
//                              Clase Juego
//      Define las reglas del juego y permite interacción entre la Snake y el Área de Juego
///////////////////////////////////////////////////////////////////////////////////////////////

class Juego
{
private:

    //Variables para utilización de hilos
    HANDLE hThread1;
    DWORD dwGenericThread;
    //Snake del juego
    Snake *s;
    //Variables del juego.
    int Puntos;
    //Variable aux para determinar si se perdió el juego.
    bool perdi;
    //Escenario del juego
    unsigned short int escenario;

public:
    Plataforma * p;

    Juego()
    {
        gameOver=false;
        perdi=false;
        camina=3;
        s=new Snake();
        p=new Plataforma();
        Puntos=0;
        escenario=0;
    }

    void setEscenario( unsigned short int _escenario)
    {
        escenario=_escenario;
    }

    ~Juego()
    {
        delete s;
        delete p;
    }

    //Método para cargar el escenario seleccionado en el menú de escenarios.
    void CargarEscenario()
    {
        switch(escenario)
        {
        case 0:
        {
            p->Escenario0();
            break;
        }
        case 1:
        {
            p->Escenario1();
            break;
        }
        }
    }

    //Cambia el color del área de juego según el escenario.
    void ColorArea(){
        switch(escenario)
        {
        case 0:
        {
             ut::setColor(23);
            break;
        }
        case 1:
        {
             ut::setColor(11);
            break;
        }
        }
    }

   //Inicializa la partida y desarrolla la lógica del juego.
    int Comienzo()
    {
        gameOver=false;
        perdi=false;
        s->Inicia(1,2);
        p->Genera();
        CargarEscenario();
        camina=3;
        Puntos=0;
        //Creamos el hilo que se encarga de detectar las teclas.
        hThread1 = CreateThread(NULL,0,Tecla,NULL,0,&dwGenericThread);
        if(hThread1 == NULL)
        {
            DWORD dwError = GetLastError();
            cout<<"Error al iniciar Juego"<<dwError<<endl ;
            return 0;
        }
        ut::gotoxy(0,0);
        DibujaSnake();
        Comida();
        cout<<p->toString();
        //Bucle principal del juego.
        while(!gameOver)
        {
            if(!pauseGame && Puntos<maxPoints)
            {
                if(camina==3||camina==4)
                    Sleep(100);
                else
                   Sleep(130);
                ut::gotoxy(0,0);
                s->Camina(camina);
                DibujaSnake();
                cout<<p->toString();
                ut::gotoxy(65,19);
                 ut::setColor(12);
                cout<<"Pts: "<<Puntos;
                ColorArea();
            }else
                gameOver=true;
        }
        if(perdi)
        {
            ut::gotoxy(15,22);
            ut::setColor(12);
            cout<<"PERDISTE!! Juego Terminado"<<endl;
        }else{
            ut::gotoxy(15,22);
            ut::setColor(0x02);
            cout<<"GANASTE!! Juego Terminado"<<endl;
        }
        ut::setColor(15);
        WaitForSingleObject(hThread1,INFINITE);
        return 0;
    }

    //Método para dibujar el Snake, validar si crece o si tuvo alguna colisión.
    void DibujaSnake()
    {
        try
        {
            CargarEscenario();
            //Avanza el snake y valida si debe crecer.
            for (s->Inicio(); s->Final()!=false; s->Avanzo())
                if(p->setSector(s->Contenido().getY(),s->Contenido().getX(),piel))
                {
                    s->Crece(camina);
                    Puntos+=10;
                    Comida();
                }
        }
        catch(...)
        {
            for (s->Inicio(); s->Final()!=false; s->Avanzo())
                try
                {
                    p->setSector(s->Contenido().getY(),s->Contenido().getX(),piel);
                }
                catch(...) {}
            pauseGame=false;
            gameOver=true;
            perdi=true;
        }
    }

    //Método para posicionar la nueva comida de la snake.
    // Basado en valores random siempre y cuando el sector a posicionar este vacío.
    void Comida()
    {
        int x=0;
        int y=0;
        do
        {
            x=rand()%limX;
            y=rand()%limY;
        }
        while(p->getSector(x,y)==(char)pared||p->getSector(x,y)==(char)piel);
        p->setSector(x,y,comida);
    }

    //Método para mostrar menu informativo lateral.
    void MenuInfo()
    {
         ut::setColor(14);
        ut::gotoxy(60,0);
        cout<<"--------------------";
        ut::gotoxy(63,1);
        cout<<"Juego de Snake";

        ut::gotoxy(63,4);
        cout<<"Instrucciones: ";
        ut::gotoxy(65,6);
        cout<<char(24)<<" = Arriba.";
        ut::gotoxy(65,7);
        cout<<char(25)<<" = Abajo.";
        ut::gotoxy(65,8);
        cout<<char(26)<<" = Der.";
        ut::gotoxy(65,9);
        cout<<char(27)<<" = Izq.";
        ut::gotoxy(65,10);
        cout<<"P = Pausa.";
        ut::gotoxy(65,11);
        cout<<"Esc = gameOver.";

        ut::gotoxy(63,15);
        cout<<"Creado por:"<<endl;
        ut::gotoxy(63,16);
        cout<<"Ing. Greivin Ch.";
        ut::gotoxy(60,17);
        cout<<"--------------------";
        ut::gotoxy(65,19);
         ut::setColor(12);
        cout<<"Pts: "<<Puntos;
        // ut::setColor(23);
        ColorArea();
    }

};

Por último la clase interfaz, la cual brinda los menús principales para iniciar el juego y seleccionar el tipo de escenario a jugar. Existe una relación de agregación mediante la cual la interfaz y los menús pueden interactuar con el juego. Este es el código de la clase:

#include "../Juego/Juego.cpp"

#define ARR 72
#define ABJ 80
#define DER 77
#define IZQ 75
#define ENT 13
#define ESC 27

class Interfaz
{
private:

    Juego * nuevo;
public:

    /*Constructor de la Interfaz del juego*/
    Interfaz()
    {
        nuevo=new Juego();
    }

    /*Menu Principal*/
    void Menu()
    {
        ut::Portada();
        int b=1;
        int men=0;
        bool salir=false;
        system("CLS");
        while(!salir)
        {
             ut::setColor(14);
            ut::gotoxy(25,2);
            cout<<"MENU SNAKE"<<endl;
            cout<<"--------------------------------------------------------------------------------"<<endl;
            if(b==1)
                 ut::setColor(5);
            cout<<"1->Juego Nuevo"<<endl<<endl;
             ut::setColor(14);
            if(b==2)
                 ut::setColor(5);
            cout<<"2->Escenario"<<endl<<endl;
             ut::setColor(14);
            ut::gotoxy(15,20);
            cout<<"Utilice las teclas "<<char(24)<<" "<<char(25)<<" press enter para selecionar";
            ut::gotoxy(1,23);
            cout<<"ESC/Salir"<<endl;
            men=ut::GetKey();
            if(men==ARR||men==ABJ||men==ESC||men==ENT)
            {
                if(men==ARR)
                    b--;
                if(men==ABJ)
                    b++;
                if(men==ESC)
                {
                    salir= ut::Salir();
                }
                if(men==ENT)
                {
                    switch (b)
                    {
                    case 1:
                    {
                        system("CLS");
                        nuevo->MenuInfo();
                        nuevo->Comienzo();
                        ut::gotoxy(15,23);
                        system("PAUSE");
                        system("CLS");
                        break;
                    }
                    case 2:
                    {
                        MenuEscenario();
                        system("CLS");
                        break;
                    }

                    }//fin del switch
                }//fin del if(men==ENT)
            }
            else
                cout<<'\a';
            if(b==0)
                b=2;
            if(b==3)
                b=1;
        }//fin del while
    }

    /*Menu de Escenarios*/
    void MenuEscenario()
    {
        int b=1;
        int men=0;
        system("CLS");
        while(men!=27)
        {
            ut::setColor(14);
            ut::gotoxy(25,2);
            cout<<"MENU ESCENARIO"<<endl;
            cout<<"--------------------------------------------------------------------------------"<<endl;
            if(b==1)
                 ut::setColor(5);
            cout<<'\t'<<'\t'<<"1->Clasico"<<endl<<endl;
             ut::setColor(14);
            if(b==2)
                 ut::setColor(5);
            cout<<'\t'<<'\t'<<"2->Moderno"<<endl<<endl;
             ut::setColor(14);
            ut::gotoxy(15,20);
            cout<<"Utilice las teclas "<<char(24)<<" "<<char(25)<<" press enter para selecionar";
            ut::gotoxy(1,23);
            cout<<"ESC/Volver"<<endl;
            men=ut::GetKey();
            if(men==ARR||men==ABJ||men==ESC||men==ENT)
            {
                if(men==ARR)
                    b--;
                if(men==ABJ)
                    b++;
                if(men==ENT)
                {
                    switch (b)
                    {
                    case 1:
                    {
                        nuevo->setEscenario(0);
                        men=27;
                        break;
                    }
                    case 2:
                    {
                        nuevo->setEscenario(1);
                        men=27;
                        break;
                    }
                }
            }
            else
                cout<<'\a';
            if(b==0)
                b=2;
            if(b==3)
                b=1;
            }
        }
    }

    ~Interfaz()
    {
        delete nuevo;
    }
};

Por último el archivo principal del programa o main únicamente crea un objeto de interfaz y llama al método Menu() , el cual ya se relaciona con el resto de objetos y clases. Veamos el código:

#include "Vista/Interfaz.cpp"

int main()
{
    Interfaz * inter=new Interfaz();
    inter->Menu();
    delete inter;
    return 0;
}

Este es todo el código del juego, trate de dejarlo lo más comentado, claro y configurable posible por si alguno quiere divertirse un rato y hacerle algunos cambios o implementar funcionalidades extra. De igual forma dejo el proyecto completo el cual está hecho con CodeBlocks por si gusta descargarlo en el siguiente link. Muchas gracias y espero que el tiempo que invertí en publicar esto le sea de provecho a alguno.

martes, 23 de diciembre de 2014

Convertir primer letra mayúscula y el resto en minúscula con SQL

En algunas situaciones es necesario convertir una cadena de caracteres de minúsculas a mayúsculas o viceversa, esto en SQL es muy fácil gracias a la funciones UPPER y LOWER. Pero qué pasa si queremos que la cadena tenga un formato de capitalización o en palabras más sencillas, la primera letra en mayúscula y el resto de letras en minúscula?? No existe una función específica en SQL Server que nos permita solucionar este problema, pero si es posible programar una que lo solucione. Por lo que compartiré el código de una solución que me ha funcionado y puede que a ustedes también. Este es el código:

DROP FUNCTION ConvierteMayusculasMinusculas
GO

CREATE FUNCTION ConvierteMayusculasMinusculas(@Cadena VARCHAR(MAX),@Delimitador VARCHAR(100)=' ')
RETURNS VARCHAR(MAX)
AS
BEGIN    

DECLARE @Resultado VARCHAR(MAX)
DECLARE @CadenaAux VARCHAR(8000)
IF CHARINDEX(@Delimitador,@Cadena,0) <> 0
BEGIN
 --Ciclo para recorrer la cadena.
 WHILE CHARINDEX(@Delimitador,@Cadena,0) <> 0
 BEGIN
     SELECT
   -- Obtenemos la primer parte de la cadena hasta el separador
   @CadenaAux=RTRIM(LTRIM(SUBSTRING(@Cadena,1,CHARINDEX(@Delimitador,@Cadena,0)-1))),
   --Obetenemos el resto de la cadena después del delimitador 
   @Cadena=RTRIM(LTRIM(SUBSTRING(@Cadena,CHARINDEX(@Delimitador,@Cadena,0)+LEN(@Delimitador),LEN(@Cadena))))
   --Evaluamos que la cadena tenga más de 2 caracteres para evitar transformar artículos (el,la,un)
   IF (LEN(@CadenaAux)>  2)
    --En caso que la cadena tenga más de 4 caracteres hacemos la conversión (Primer carácter mayúscula y los otros en minúscula)
    SET @Resultado= ISNULL(@Resultado,'') + UPPER(SUBSTRING(@CadenaAux,1,1)) +  LOWER(SUBSTRING (@CadenaAux,2,LEN(@CadenaAux)-1)) + ' '
   ELSE
    SET @Resultado= ISNULL(@Resultado,'') + LOWER( @CadenaAux) + ' '
 END
 END   
-- Validación necesaria en el caso que las cadenas solo tengan una palabra y utilizada al final de la conversión.
IF  CHARINDEX(@Delimitador,@Cadena,0)  = 0
BEGIN
 SET @Resultado= ISNULL(@Resultado,'') + UPPER(SUBSTRING(@Cadena,1,1)) +  LOWER(SUBSTRING (@Cadena,2,LEN(@Cadena)-1)) + ' '
END
RETURN LTRIM(RTRIM(@Resultado))
END  
GO

Como podemos ver en el código anterior se reciben dos parámetros que sería la cadena a convertir y un delimitador el cual sería el carácter que separa cada palabra a convertir (La opción default es un espacio en blanco). Veamos el resultado de una prueba para convertir la cadena "juan arias soto" por "Juan Arias Soto", utilizando como delimitador el carácter default.






El único inconveniente que podemos encontrar en el código es que solo valido palabras con menos de dos caracteres para no convertir a mayúsculas por lo que palabras como "del, las, etc." van a sufrir la conversión, en este caso si se aumenta el largo de caracteres de la validación podría funcionar pero puede que algunas palabras que no deseamos convertir lo hagan y viceversa. Para mi podría hacerse una mejora en la validación con una pequeña estructura que controle las palabras que no deben convertirse según el caso y así habrá seguridad de que cualquier palabra ajena a dicha estructura cambiara. Bueno espero les sea de ayuda a alguno este pequeño código, Gracias y Saludos. 

lunes, 15 de diciembre de 2014

Concatenar registros de columna en cadena (COALESCE)

En un post anterior vimos cómo realizar un split en SQL, con el cual dada una cadena separada por un carácter delimitador es posible dividirla y devolverla a manera de consulta de una tabla.
Ahora me gustaría compartir  un pequeño código que permite hacer lo contrario, ósea en base a una consulta de una tabla, tomar una columna específica y generar un string concatenando cada row de la consulta en la cadena y separándola con un carácter delimitador.

Veamos un ejemplo para entender la dinámica del problema y su respectiva solución. Para esta haremos uso de  una tabla temporal de Usuarios, la cual tiene una columna Nombre que su consulta nos devuelve lo siguiente:



Ahora queremos concatenar todos estos nombres en una cadena, cada uno separado por coma. Algo así:

"Juan,  Carlos, Sofía, Ana, Paco, Viviana, Mía, Keylor"

Para la solución haremos uso de la función COALESCE la cual nos brinda esta funcionalidad. El único inconveniente es que todos los valores de la columna deben ser distintos de NULL para que esta solución funcione de manera correcta.

Veamos el código del ejemplo  Aclaro que se utilizó una tabla temporal solo para modo de ejemplo, pero es posible también utilizar una tabla normal de la Base de Datos.

--Tabla temporal de usuarios
DECLARE @Usuarios AS TABLE(
 Nombre VARCHAR(255)
)

--Agregamos varios datos la tabla.
INSERT INTO @Usuarios (Nombre)VALUES 
 ('Juan'),('Carlos'),('Sofía'),('Ana'),('Paco'),('Viviana'),('Mía'),('Keylor')

--Variable donde se guardara la cadena generada.
DECLARE @STR_ACTIVIDADES AS VARCHAR(MAX)

--Select para crear la cadena basados en la columna nombre de la tabla @Usuarios 
--Concatenamos en la cadena @STR_ACTIVIDADES cada row separado por coma.
SELECT  
 @STR_ACTIVIDADES = COALESCE(@STR_ACTIVIDADES + ', ', '') + Nombre
FROM 
 @Usuarios
 
 --Imprimimos la cadena para ver el resultado.
PRINT @STR_ACTIVIDADES

Ahora veamos el resultado que obtenemos al ejecutar el código anterior.


Listo!!! Problema solucionado....
Esto puede ser muy útil a la hora de presentar reportes donde se requiera  resumir información de varias rows en un mismo campo. Espero que el código les sea de ayuda, sin más por el momento me despido hasta pronto!!