domingo, 22 de febrero de 2015

Programar columna acumulada SQL en dos líneas

Buenas estoy de vuelta por acá en esta ocasión quiero compartirles un forma bastante interesante de como programar el acumulado de una columna para una tabla en SQL. Existen varias formas de programar esto, la más común y que pienso a la mayoría de nosotros se nos ocurriría sería crear un cursor el cual vaya recorriendo la columna y acumulado con una variable auxiliar. Algo como esto:

--Declaración de la tabla temp a utilizar para el ejemplo.
DECLARE @TABLA AS TABLE (
 CODIGO INT NOT NULL IDENTITY(1,1),
 DATO DECIMAL(10,2),
 ACUMULADO DECIMAL(10,2) DEFAULT 0
)
--Agregamos algunos datos a la tabla.
INSERT INTO  @TABLA (DATO) 
 VALUES (5),(10),(8),(9),(15),(20)

--Declaración de variables y cursor a utilizar 
DECLARE @Cod INT,@Dato DECIMAL (10,2),@DatoAcum DECIMAL (10,2)=0
DECLARE C2 CURSOR FOR SELECT CODIGO,DATO FROM @TABLA
OPEN C2
FETCH C2 INTO @Cod,@Dato
--Ciclo para recorrer el cursor.
WHILE @@FETCH_STATUS = 0
BEGIN
 --Acumulamos el dato.
 SET @DatoAcum += @Dato
 --Actualizamos la columna  con los datos acumulados hasta el momento
 UPDATE @TABLA SET ACUMULADO=@DatoAcum WHERE CODIGO=@Cod
 
 FETCH C2 INTO @Cod,@Dato 
END
CLOSE C2
--Obtenemos el resultado
SELECT * FROM @TABLA


Veamos el resultado del código



Como podemos ver es una implementación valida, resuelve nuestro problema y acumula la columna de manera exitosa, pero a pesar de esto quise buscar otra manera más corta de poder resolver la misma situación y fue donde encontré como hacerlo con únicamente un par de líneas. Para esto utilice una variable del mismo tipo de dato de la columna a acumular, y una sentencia update la cual encierra toda la ciencia del código. Sin más explicación veamos el código que es bastante sencillo.

--Declaración de la tabla temp a utilizar para el ejemplo.
DECLARE @TABLA AS TABLE (
 CODIGO INT NOT NULL IDENTITY(1,1),
 DATO DECIMAL(10,2),
 ACUMULADO DECIMAL(10,2) DEFAULT 0
)
--Agregamos algunos datos a la tabla.
INSERT INTO  @TABLA (DATO) 
 VALUES (5),(10),(8),(9),(15),(20)
 
--Declaración de variables a utilizar
DECLARE @ACUM DECIMAL(10,2)=0
--Sentencia UPDATE la cual utiliza la variable de tal forma que permite calcular el acumulado
UPDATE @TABLA SET  @ACUM=ACUMULADO=@ACUM+DATO
  
--Obtenemos el resultado
SELECT * FROM @TABLA

Veamos el resultado del código


Y listo en solo un par líneas y de una manera mucho más eficiente logramos obtener el mismo resultado que cuando utilizamos el cursor. Espero el código compartido les sea de ayuda y logren ahorrarse algunas líneas de código cuando tengan que resolver algo similar. Gracias por su tiempo!!

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!!

domingo, 23 de febrero de 2014

Split en SQL (Convertir cadena en consulta)

Como podríamos separar en SQL una cadena con un formato especifico, la cual contiene varios datos separados por un delimitador? Bueno es fácil se realiza un split a la cadena.
 El problema es que SQL no tiene una función especifica para realizar esta acción, por lo que una solución seria implementar nuestra propia función Split. Lo que haremos es crear una función la cual reciba como parámetros una cadena y un delimitador tipo varchar,  y esta separara cada uno de los valores de la cadena  y los insertara como filas a una variable tipo tabla, desde la cual ya podemos utilizarlos como si fueran registros separados.

Veamos el código de esta función:

CREATE FUNCTION Split 
(
 @CADENA AS VARCHAR (MAX),
 @DELIMITER AS VARCHAR(10)
)
RETURNS @tt TABLE(item VARCHAR(255))
AS
BEGIN
 SET @CADENA+= @DELIMITER
 DECLARE @AUX AS VARCHAR(255)
 WHILE (LEN(@CADENA) > 0) 
 BEGIN
  SET @AUX= SUBSTRING(@CADENA, 1,CHARINDEX(@DELIMITER, @CADENA)-1)
  INSERT INTO @tt VALUES(@AUX)
  SET @CADENA= SUBSTRING(@CADENA,LEN(@AUX)+2,Len(@CADENA))
 END
 RETURN;
END

Ahora veamos un ejemplo supongamos que tenemos una cadena con nombres de personas separados por coma, entonces ejecutemos la función para ver su resultado.




Como podemos ver obtuvimos la tabla con todos los rows que teníamos en la cadena. Ahora podemos hacerlo con cualquier cadena inclusive con cualquier delimitador. Bueno me despido y espero que les sea de ayuda el código.


jueves, 5 de diciembre de 2013

Fundamentos Juegos en Consola C++

Muchas personas cuando empiezan a entrar en el mundo de la programación tienen la iniciativa o inquietud de ver como se implementa un juego, pues bueno para todas esas personas en este post les vengo a compartir un código muy básico, el cual es el fundamento de casi cualquier juego que involucre el movimiento de  algún elemento por parte del Usuario, como por ejemplo: Mario, Pac Man, Bomber Man, Snake, Space Invaders, entre muchos otros.

Bueno el ejemplo es un código que nos permite mover un objeto (en este caso un muñeco) mediante las flechas (Arriba, Abajo, Derecha, Izquierda) por una área definida en la pantalla. Acá podemos ver una imagen alusiva al ejemplo.

Para la implementación de este código y cualquier otro a futuro que utilice el mismo paradigma primero debemos conocer sobre la programación orientada a objetos (POO), dado a que prácticamente cada componente del juego lo manejaremos como un objeto. Pero que es un objeto o que es POO?, bueno básicamente es la abstracción de un objeto del mundo real a un mundo virtual o programado. Cada objeto tiene sus propiedades (atributos) y funciones en un mundo real, tomaremos las funciones y propiedades que necesitemos según la implementación y lo encapsularemos en una clase, luego de esta podremos crear distintos objetos. Es importante tomarse el tiempo para pensar cómo se estructuraran las clases y cuales necesitaremos, dado a que es la estructura principal del juego.

El  segundo aspecto importante son las coordenadas, estas se representan mediante dos valores, "x" que representa la anchura en una pantalla y "y" la altura. Mediante estos dos valores podemos posicionar un objeto en algún lugar de la pantalla. Es importante que cada objeto dentro de sus propiedades tengan las coordenadas, para que el mismo conozca su ubicación y basado en ella pueda tomar decisiones. Otro aspecto importante a recalcar es que entre más alto sea el valor x este estará más hacia la derecha nuestra y entre menor sea el valor de y más hacia abajo de la pantalla.

El tercer aspecto que debemos conocer es el ciclo de vida del juego, todo juego tiene un ciclo de vida desde donde se inicia hasta que ocurra un evento de GameOver el cual es la regla que hace que el juego termine como por ejemplo que se hayan terminado las vidas de personaje del juego o alguna otra regla según la lógica del juego. Para esto se hace la implementación de un daemon, que básicamente es un ciclo dentro del cual deben llamarse todas las acciones que ocurren en el juego, y la condición de parada de dicho ciclo seria el evento GameOver. Para este ejemplo como no es una implementación completa de un juego no existe una regla en la cual termine, sino que esto ocurre cuando el usuario presione la tecla "Esc".

Con estos tres aspectos claros podemos realizar cualquier implementación de un juego como los ejemplos dados al inicio, solo se debe jugar un poco con los objetos y principalmente con las coordenadas de los objetos que es el alma del juego. Ya explicado estos principios, continuare con la estructura y posteriormente el código fuente del ejemplo del post.

Antes que nada el ejemplo hace uso de un encabezado (archivo.h) el cual tiene algunas funciones básicas como cambiar el color de la consola, posicionar el cursor de impresión de consola en una coordenada específica, ocultar el cursor de la consola, entre otras.  Estas funciones son necesarias en el resto de la implementación del ejemplo, y la idea es que este mismo encabezado pueda utilizarse en ejemplos futuros sin necesidad de cambiarse volver a programar el código. Este es el código de dicho encabezado:
////////////////////////////////////////////
// Definición Bibliotecas y definiciones
// q' utilizaremos en el código
///////////////////////////////////////////
#include <windows.h>
#include <conio.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   0x47//Color de la caja de la pantalla salir. 
#define OPC_SELEC_SALIR    0x4E//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");
}

};
Básicamente este ejemplo cuenta con dos clases Muñeco y Área (como lógica principal). La primera (Muñeco),  tiene como propiedades las coordenadas del muñeco, y como funciones moverse, ValidarMovimiento, Dibujar y Borrar, y la función de clase lógicamente es todo relacionado con el objeto, si queremos agregar más funcionalidad al muñeco, únicamente se agregan las funciones o propiedades que el mismo necesite para dicha función en esta clase, y se llaman desde la interfaz en el método principal (más adelante se encuentra esta parte).   Este es el código de la clase Muñeco:
#include "stdafx.h"
#include <iostream>
#include "UtilidadesInterfaz.h"

#define ut UtilidadesInterfaz //Definición de espacio nombre utilizado para Utilidades Interfaz.

//Definiciones de skins ASCII utilizadas en el muñeco.
#define Cabeza         1 //Skin  utilizado para la cabeza
#define ManoIzquierda  42//Skin  utilizado para la mano Izquierda
#define ManoDerecha    42//Skin  utilizado para la mano Derecha
#define Cuerpo         176//Skin utilizado para el Tronco o Cuerpo
#define Piernas        19//Skin  utilizado para las piernas
//Definiciones Colores muñeco.
#define ColPiel    0x0D //Color de la Piel del muñeco. Aplica para cabeza y manos
#define ColCuerpo  0x0A//Color del cuerpo del mueñeco.
#define ColPies    0x0E//Color de los pies del mueñeco.
////////////////////////////////////////////////////////////////////////////////////////////////
//                              Clase Muneco
//     Crea un objeto con forma de muñeco que es el que se moveremos con las flechas.
///////////////////////////////////////////////////////////////////////////////////////////////

class Muneco{
private:
 //Coordenadas donde se encuentra el Objeto.

 int x,y;
public:
 //Constructor sin parametros del Objeto. Lo posiciona en las coordenadas 00.
 Muneco(){
  x=0;
  y=0;
 }

 //Contructor con parametros del Objeto. Lo posiciona en las coordenadas XY.
 Muneco(int _x,int _y){
  x=_x;
  y=_y;
 }

 //Get de la Coordenada X
 int CordX(){return x;}

 //Get de la Coordenada Y
 int CordY(){return y;}

 //Método para borrar e objeto de la Consola. Es el mismo que Mostrar pero ponemos caracteres vacíos en esa posición.
 void Borrar(){
  //Borramos la cabeza del mueñeco
  ut::gotoxy(x+2,y);
  printf(" ");

  //Borramos las manos del muñeco, primero la Izq luego la derecha.
  ut::gotoxy(x,y+1);
  printf(" ");

  ut::gotoxy(x+4,y+1);
  printf(" ");

  //Borramos el tronco del muñeco.
  ut::gotoxy(x+1,y+1);
  printf("   ");
  ut::gotoxy(x+2,y+2);
  printf(" ");

  //Borramos los pies del mueñeco
  ut::gotoxy(x+2,y+3);
  printf(" "); 
 }

 //Método que imprime el muñeco en las coordenadas que se encuentra.
 void Mostrar(){ 
  //Imprimimos la cabeza del muñeco.
  ut::setColor(ColPiel);
  ut::gotoxy(x+2,y);
  printf("%c",Cabeza);

  //Imprimimos las manos del muñeco, primero la Izq luego la derecha.
  ut::gotoxy(x,y+1);
  printf("%c",ManoIzquierda);

  ut::gotoxy(x+4,y+1);
  printf("%c",ManoDerecha);

  //Imprimimos el tronco del muñeco.
  ut::setColor(ColCuerpo);
  ut::gotoxy(x+1,y+1);
  printf("%c%c%c",Cuerpo,Cuerpo,Cuerpo);
  ut::gotoxy(x+2,y+2);
  printf("%c",Cuerpo);

  //Imprimimos los pies del mueñeco
  ut::gotoxy(x+2,y+3);
  ut::setColor(ColPies);
  printf("%c",Piernas);
 }

 //Método para mover el objeto dentro de las coordenadas.
 void Mover(int px, int py){
   Borrar();//Borro el objeto anterior
   //Muevo las coordenadas a las nuevas.
   x=px;
   y=py;
   //Redibujo el Objeto en las nuevas coordenadas.
   Mostrar();
 }

 //Función que valida el movimiento del objeto y verifica si el nuevo movimiento a realizar no sobre pasa los limites definidos.
 /*TENER EN CUENTA ESTOS DOS ASPECTOS CUANDO SE VALIDAN LOS MOVIMIENTOS*/
 //La coordenada X tiene el punto de referencia en la mano Izquierda del muñeco, para validar si no sobrepaso el Limite Derecho debe sumársele  4, dado a que  hacia de la derecha de la mano Izquierda hay 4 caracteres más.
 //La coordenada Y tiene el punto de referencia en la Cabeza del  muñeco, para validar si no sobrepaso el Limite Inferior debe sumársele  3, dado a que para abajo de la cabeza hay 3 caracteres más.
 bool MovimientoValido(int px,int py,int limIzq,int limDer,int limSup,int limInf){
  //En caso que sobrepase el Limite Superior
  if(py<=limSup)return false;
  //En caso que sobrepase el Limte Inferior
  if(py+3>=limInf)return false;
  //En caso que sobrepase el Limite Izquierda
  if(px<=limIzq)return false;
  //En caso que sobre pase el Limite Derecha
  if(px+4>=limDer)return false;
 }
};
La segunda clase (Área), dibuja el Área en la que puede movilizar el muñeco, define los limites de la misma. En este caso las propiedades serian los 4 limites y como funciones tenemos dibujarArea únicamente. Este es el código de la clase:
#include "stdafx.h"
#include <iostream>
#include "Muneco.cpp"
//Definiciones de Skins Ascii utilizados definir el área donde se mueve el Objeto.
#define LIM_HOR 205   //Caracter Limite Horizontal
#define LIM_VER 186   //Caracter Limite Vertical
#define ESQ_SUP_IZQ 201  //Caracter Esquina Superior Izquierda
#define ESQ_SUP_DER 187  //Caracter Esquina Superior Derecha
#define ESQ_INF_IZQ 200     //Caracter Esquina Inferior Izquierda
#define ESQ_INF_DER 188     //Caracter Esquina Inferior Derecha


////////////////////////////////////////////////////////////////////////////////////////////////
//                              Clase Área Juego
//     Crea un objeto área la cual define el área donde puede moverse el Objeto (Muñeco)
///////////////////////////////////////////////////////////////////////////////////////////////
class AreaJuego{
private:
 //Variables que almacena las coordenadas limites hasta donde puede moverse el objeto.
 int LIM_SUP;           //Coordenada Limite Inferior
 int LIM_INF;           //Coordenada Limite Superior
 int LIM_IZQ;          //Coordenada Limite Izquierda
 int LIM_DER;          //Coordenada Limite Derecha
public:

 //Contructor por defecto del Área, en este caso crea un área cuadrada
 AreaJuego(){
  LIM_SUP=2;
  LIM_INF=20;
  LIM_IZQ=2;
  LIM_DER=20;
 }

 //Constructor con parametros del área, para poder crear un área segun las coordenadas deseadas
 //Debe tenerse cuidado que las coordenadas no sean mayores al alto o ancho de la cosola, sino puede que no funcione correctamente.
 AreaJuego(int limSup,int limInf,int limIzq, int limDer){
  LIM_SUP=limSup;
  LIM_INF=limInf;
  LIM_IZQ=limIzq;
  LIM_DER=limDer;
 }

 //métodos GETS Y SETS para las distintas coordenadas, en caso de cambiar una coordenada debe redibujarse el Área 
 //que el cambio pueda visualizarse.
 int getLimSup(){return LIM_SUP;}
 int getLimInf(){return LIM_INF;}
 int getLimIzq(){return LIM_IZQ;}
 int getLimDer(){return LIM_DER;}

 void setLimSup(int limSup){LIM_SUP=limSup;}
 void setLimInf(int limInf){LIM_INF=limInf;}
 void setLimIzq(int limIzq){LIM_IZQ=limIzq;}
 void setLimDer(int limDer){LIM_DER=limDer;}

 //Método para dibujar el Área donde se mueve el Objeto, se dibuja segun las Coordenadas del Área.
 void limites(){
  ut::setColor(0x02);
  //Dibuja los limites Horizontales
  for(int i=LIM_IZQ;i<LIM_DER;i++){
   ut::gotoxy(i,LIM_SUP);printf("%c",LIM_HOR);
   ut::gotoxy(i,LIM_INF);printf("%c",LIM_HOR);
  }
  //Dibuja los limites Verticales
  for(int i=LIM_SUP;i<LIM_INF;i++){
   ut::gotoxy(LIM_IZQ,i);printf("%c",LIM_VER);
   ut::gotoxy(LIM_DER,i);printf("%c",LIM_VER);
  }
  //Dibuja las Esquinas del Área.
  ut::gotoxy(LIM_IZQ,LIM_SUP);printf("%c",ESQ_SUP_IZQ);
  ut::gotoxy(LIM_IZQ,LIM_INF);printf("%c",ESQ_INF_IZQ);
  ut::gotoxy(LIM_DER,LIM_SUP);printf("%c",ESQ_SUP_DER);
  ut::gotoxy(LIM_DER,LIM_INF);printf("%c",ESQ_INF_DER);
  ut::setColor(0x0F);
 }
};
Además de esto como clase no primordial de la lógica del ejemplo, pero necesaria para su funcionamiento es la clase Interfaz  en esta unimos la clase Área y Muñeco, y  realizamos el bucle principal de vida del juego, y los procesos de solicitar e informar al usuario lo que sucede en el juego. Este es el código de la clase:
#include "stdafx.h"
#include "AreaJuego.cpp"
using namespace std;

////////////////////////////////////////////////////////////////////////////////////////////////
//                          Clase Interfaz Objeto Flechas
//     Crea un objeto con forma de muñeco que es el que se moveremos con las flechas.
///////////////////////////////////////////////////////////////////////////////////////////////
class InterfazObjetoFlechas{
private:
 Muneco * obj;
 AreaJuego *area;

 
 //Método que inicia los Elementos (Área,Objeto,Info Esc)
 void IniciaElementos(){
  area->limites();//Dibujamos los limites del Área donde se mueve el Objeto.
  obj->Mostrar();//Mostramos el Objeto
  ut::gotoxy(2,22);
  cout<<"Esc/Salir";
 }

public:
 //Constructor por defecto de la clase
 InterfazObjetoFlechas (){
  //Creamos una Área de Juego donde se va a jugar.
  area = new AreaJuego(2,20,2,77);
  //Creamos un Objeto Nuevo con las coordenadas Iniciales en el Centro del Área de la Plataforma.
  obj = new Muneco((area->getLimDer()-area->getLimIzq())/2,(area->getLimInf()-area->getLimSup())/2);
 } 

 //Destructor de la Clase
 ~InterfazObjetoFlechas (){
  delete obj;//Destruyo el Objeto
 } 

 //Método principal donde se mueve el Objeto dentro del Área.
 void metodoPrincipal(){
  ut::Portada();//Imprimimos la portada de la Aplicación
  ut::ocultaCursor();//Ocultamos el cursor
  system("CLS");
  IniciaElementos();
  bool Salir = false; //Bandera que me indica si la aplicación debe cerrarse.
  int tecla =0; //Variable para almacenar la Tecla digitada.
  //Inicializamos los valores de las Coordenadas digitadas por el Usuario.
  int x=obj->CordX();
  int y=obj->CordY();
  while(!Salir){
   //Detectamos si alguna tecla es precionada mediante funcion kbhit de Conio.h
   if(kbhit()){
    //Guardamos la tecla presionada a la variable  tecla.
    tecla=getch();
    if(tecla==IZQ || tecla==DER || tecla == ARR ||tecla== ABJ){
     //Casos a evaluar con la tecla presionada para mover el objeto.
     //En caso que el objeto tenga mas funciones que moverse deben agregarse los case respectivos
     switch(tecla){
      case IZQ:x--;break;
      case DER:x++;break;
      case ARR:y--;break;
      case ABJ:y++;break;
     }
     //Validamos que que no se sobrepase los limites.
     if(obj->MovimientoValido(x,y,area->getLimIzq(),area->getLimDer(),area->getLimSup(),area->getLimInf())){
      //En caso que no haya pasado los limites movemos el objeto.
      obj->Mover(x,y);
     }else{
      //En caso que el movimiento no sea valido pongo el X y Y en las pociones anteriores antes del movimiento.
      x=obj->CordX();
      y=obj->CordY();
     }
    }else{//En caso que la tecla presionada sea distinta a alguna de las Flechas.
     if(tecla==ESC){
      //Llamamos al metodo de Salir
      Salir=ut::Salir();
      if(!Salir){//En caso que no desee Salir de la Aplicación redibujamos los limites y el Objeto.
       IniciaElementos();
      }
     }
    }
   }
   //Damos un tiempo de espera para volver a ejecutar el ciclo.Recomendado para liberar procesador.
   Sleep(30);
  }
 }
};


int _tmain(int argc, _TCHAR* argv[])
{
 InterfazObjetoFlechas * nueva = new InterfazObjetoFlechas();//Nuevo Objeto de Interfaz
 nueva->metodoPrincipal();//Llamamos al metodo principal.
 delete nueva;//Destruimos el objeto Interfaz.
 return 0;
}

Ahora lo único que necesitamos es un poquito de creatividad y dedicación para crear un juego simple como el de este ejemplo, Pronto adjuntare ejemplos de juegos un poco más elaborados. Acá dejo el código Fuente del ejemplo completo:  Descargar

lunes, 25 de noviembre de 2013

Interfaz con Flechas Consola C++

Uno de los aspectos más tediosos a la hora de construir nuestras aplicaciones de consola, es crear la interfaz para la aplicación (menús), como solo contamos con el teclado como dispositivo de entrada, nuestras pantallas se convierten en el típico ambiente donde se le solicita al usuario digitar el número de la opción a seleccionar. Esto aparte de ser engorroso para el usuario, nos deja varios aspectos por resolver, como que pasa si se digita una opción no existente en el menú?, o peor aún si se digitan letras en lugar de números?, entre muchas otras. Bueno existen varias formas para resolver estos casos, pero de una u otra manera este tipo de ambientes siguen siendo aburridos y poco amigables con el usuario.

En este post  les ofrezco una mejor manera para crear nuestros menús, y así tener una interfaz más amigable y natural en nuestro programa. Esto lo lograremos haciendo uso de algunas teclas como lo son las  Flechas (Arriba, Abajo, Derecha e Izquierda) para desplazarnos en las opciones del menú, el Enter para seleccionar una opción, y el Esc para salir del programa. Utilizaremos también algunos colores para distinguir en cual opción nos encontramos del menú, y para mejorar la apariencia de las pantallas.

Acá podemos ver cómo quedaría nuestra interfaz en última instancia:

Básicamente la implementación de este Menú consta dos clases "InterfazConFlechas.cpp"  y "UtilidadesInterfaz.h", cómo la idea del este post no es explicar un paso a paso de cómo crear el menú, haré una explicación breve sobre el contenido cada una de las clases, y al final encontraran el link de descarga con el código fuente en un proyecto de Visual 2010. El código esta lo más ordenado y comentado posible para su fácil entendimiento.

La primera clase "InterfazConFlechas.cpp" es donde se implementa la lógica principal (Método MenuPrincipal) que es el que vemos en la imagen anterior. Para este ejemplo cada una de las opciones en caso de seleccionarlas muestra una pantalla donde nos indica la opción seleccionada (algo muy sencillo), pero de igual forma en cada opción podemos agregar otro método con un menú similar.

 La otra clase "UtilidadesInterfaz" es donde se implementan algunas funciones necesarias utilizadas en InterfazConFlechas. Tiene una relación de herencia con InterfazConFlechas, para que la misma pueda utilizar los métodos y funciones de esta. Entre los métodos y funciones que tiene están:

  • gotoxy() Posiciona el cursor donde voy a imprimir según coordinadas X,Y subministradas
  • getKey() Obtiene el valor de la tecla digitada, sin tener que presionar Enter.
  • ocultarCursor() Esconde la línea parpadeante de la consola, que es el cursor.
  • Salir() Pantalla de confirmación de si desea o no salir del programa.
  • Portada() Portada para el programa.
  • setColor() Cambia el color de fondo y letra de Consola para las siguientes impresiones.
Este último método setColor(), el valor de color de fondo y letra al que se desea cambiar  debe tener un formato hexadecimal (de 0 a F, donde 0 es negro y F blanco). Se deben pasar dos valores el primero para el fondo y el segundo para la letra. Acá podemos ver las posibles combinaciones:

En el caso que la combinación de color tenga un valor con letra como 1F,  al método setColor se le debe pasar 0x1F en lugar de pasarle únicamente 1F.

Desde que descubrí esta manera para hacer interfaces en Consola, la utilice en todos mis tareas y trabajos de programación de la Universidad, es bastante fácil de implementar y le da un gran plus a la aplicación, espero también les sea de ayuda. Como nota dado a que para algunas funciones se utiliza la librería "windows.h", solo funciona en sistemas Windows.. LASTIMA!!!, si alguno quiere hacer el aporte de cómo implementar esto mismo sin hacer uso de esta librería bienvenido sea.

Acá pueden obtener el código fuente:   Descargar Aquí