Juego Hanoi en C++ [Allegro 5]

tienda de móviles online
OP

BBR~

Un héroe de otoño
Moderador
Editor
Mensajes
10.487
Calificaciones
3K 35
Puntos
625
Ubicación
CDMX
#1
Sencillo juego de Hanoi hecho en C++ con Allegro 5


Tiene aproximadamente año y medio que no veo nada de C/C++ y estaba buscando un motor para empezar a desarrollar juegos. Allegro no es lo mío pero recordé que hace un tiempo hice este mismo juego de Hanoi para un proyecto de programación pero en ese momento usé winbgim y déjenme decirles que es un mal que no le deseo a nadie. Algo he aprendido a lo largo de este tiempo así que recrearlo me tomó algunas horas, la mayoría recordando cómo usar C y corrigiendo errores por no usar ; (porque python). No pretendo seguir usando Allegro pero sí quiero terminar este proyecto en un futuro cercano e inclusive hacer un tutorial al respecto (o por lo menos comentar correctamente todo el cógido).

Objetivo del juego

Tienes que conseguir pasar todos los discos desde la primera torre hasta la última torre. Los discos tienen que estar ordenados desde el más grande en la base hasta el más pequeño en la punta. Puedes mover un disco a la vez y para colocarlo en una torre es necesario que ésta esté vacía o que el disco que tiene en el tope sea más grande que el disco que intentas meter. No hay límite de tiempo.

Controles
  • Flecha ARRIBA: Levantar un disco. Si ya se tiene uno, entonces no hace nada.
  • Flecha ABAJO: Bajar un disco. Si no se tiene ninguno, entonces no hace nada.
  • Flecha IZQUIERDA/DERECHA: Mover el cursor o el disco, según corresponda.

Cosas planeadas
  • Implementar una pantalla de victoria y no que se cierre el programa
  • Implementar un sistema de puntuaciones.
  • Mejorar la precisión del tiempo y mostrar mili-segundos.
  • Añadir una opción para jugar con más discos.
  • Limpiar el código y corregir todas las atrocidades que hice por no recordar cómo usar C++ correctamente.
  • Agregar un menú al juego.

Código del juego

C++:
#include <stdio.h>
#include <iostream>
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>

#define NEGRO  al_map_rgb(0,0,0)
#define VERDE  al_map_rgb(34,139,34)
#define ROJO  al_map_rgb(178,34,34)
#define AZUL  al_map_rgb(70,130,180)
#define NARANJA  al_map_rgb(255,140,0)
#define GRIS  al_map_rgb(47,79,79)

#define ANCHO 640
#define ALTO 480

void dibujarBases(int, int, int, int);
void dibujarDiscos(int [][3], int, int, int, int);
void dibujarCursor(int[], int, int, int, int);
ALLEGRO_COLOR obtenerColor(int);

ALLEGRO_DISPLAY *display;
ALLEGRO_EVENT_QUEUE *event_queue;
ALLEGRO_TIMER *timer;

int main()
{
    int redraw = 1;

    //Inicializando Allegro y todos los addons que vamos a utilizar
    al_init();
    al_init_primitives_addon();
    al_init_image_addon();
    al_install_keyboard();

    if (!al_init_font_addon()){
        std::cout << "Error al inicializar las fuentes" << std::endl;
        return 1;
    }

   if (!al_init_ttf_addon()){
        std::cout << "Error al inicializar el TTF addon" << std::endl;
        return 1;
   }

    ALLEGRO_FONT *fuente = al_load_font("wonder.ttf", 20, 0);

    //Creamos la ventana con la resolución deseada
    display = al_create_display(ANCHO, ALTO);

    //Iniciamos una cola de eventos que va a registrar todo lo que pase
    event_queue = al_create_event_queue();

    /*Creamos un timer en el que decidimos a qué velocidad correrá el juego
    (60 cuadros por segundo, en este caso)*/
    timer = al_create_timer(1.0 / 60);

    al_register_event_source(event_queue, al_get_display_event_source(display));
    al_register_event_source(event_queue, al_get_timer_event_source(timer));
    al_register_event_source(event_queue, al_get_keyboard_event_source());

    al_start_timer(timer);

    int numeroDiscos = 3;
    int torres [3][3];

    torres[0][0] = 3;
    torres[0][1] = 2;
    torres[0][2] = 1;

    for(int i = 1; i < 3; i++){
        for(int j = 0; j<numeroDiscos; j++){
            torres[i][j] = 0;
        }
    }

    int cursor [] = {-1,0,0};

    int contador = 0;


    while (1){

        ALLEGRO_EVENT event;
        al_wait_for_event(event_queue, &event);

        if(torres[2][0] == 3 && torres[2][1] == 2 && torres[2][2] == 1){
            al_draw_filled_circle(20, 20, 35, NARANJA);
            std::cout << "Victoria\n";
            std::cout << "Tiempo: " << contador/60 << " segundos.";
            break;
        }

        if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            break;

        if (event.type == ALLEGRO_EVENT_KEY_DOWN){
            int cPos = 0;
            for(; cPos<3; cPos++){
                if(cursor[cPos] != 0){
                    break;
                }
            }

            if (event.keyboard.keycode == ALLEGRO_KEY_LEFT){
                if (cPos > 0){
                    cursor[cPos-1] = cursor[cPos];
                    cursor[cPos] = 0;
                }
            }


            if (event.keyboard.keycode == ALLEGRO_KEY_RIGHT){
                if(cPos<2){
                    cursor[cPos+1] = cursor[cPos];
                    cursor[cPos] = 0;
                }
            }

            if (event.keyboard.keycode == ALLEGRO_KEY_UP){
                if(cursor[cPos] == -1){
                    int topDisc = 2;
                    for(; topDisc >= 0; topDisc--){
                        if(torres[cPos][topDisc] != 0){
                            cursor[cPos] = torres[cPos][topDisc];
                            torres[cPos][topDisc] = 0;
                            break;
                        }
                    }
                }
            }

            if (event.keyboard.keycode == ALLEGRO_KEY_DOWN){
                if(cursor[cPos] != -1){
                    int topDisc = 0;
                    for(; topDisc<3; topDisc++){
                        if(torres[cPos][topDisc] == 0){
                            break;
                        }
                    }
                    if(topDisc == 0){
                        torres[cPos][topDisc] = cursor[cPos];
                        cursor[cPos] = -1;
                    }else if(topDisc > 0){
                        if(torres[cPos][topDisc-1] > cursor[cPos]){

                            torres[cPos][topDisc] = cursor[cPos];
                            cursor[cPos] = -1;
                        }
                    }
                }
            }
        }
        redraw = 1;


        if (redraw){
            al_clear_to_color(al_map_rgb(255, 255, 255));

            al_draw_textf(fuente, NEGRO, 100, 20, ALLEGRO_ALIGN_LEFT, "Tiempo %d", contador++/60);

            //Especificamos qué tan anchas, gruesas y altas queremos la base de las bases.
            int ancho = 180;
            int grosor = 30;
            int alto = 200;

            //Separación entre las bases.
            int separacion = 30;

            dibujarBases(ancho, grosor, alto, separacion);
            dibujarDiscos(torres, numeroDiscos, ancho, separacion, grosor);
            dibujarCursor(cursor, alto, ancho, separacion, grosor);

            al_flip_display();

            redraw = 0;
        }
    }

    al_destroy_display(display);
    al_destroy_event_queue(event_queue);
    al_destroy_timer(timer);
    //al_destroy_bitmap(mi_sprite);

    return 0;
}

void dibujarBases(int ancho, int grosor, int alto, int separacion){
    //Fórmula que calcula exactamente dónde estarán centradas las bases.
    int inicioX = (ANCHO-(ancho*3)-(separacion*2))/2;

    //Obtenemos el centro de la base
    int centro = ancho/2;

    //Coordenada Y en donde queremos que empiecen las bases.
    int inicioY = ALTO-100;


    //Dibujamos las torres
    for(int i = 0; i < 3; i++){
        int inicioRect = inicioX+(i*(ancho+separacion));

        //Dibuja la base
        al_draw_filled_rectangle(
            inicioRect, inicioY,
            inicioRect+ancho, inicioY+grosor,
            NEGRO);

        //Dibuja la barra
        al_draw_filled_rectangle(
            inicioRect+centro-(grosor/2),
            inicioY-alto,
            inicioRect+centro+1+(grosor/2),
            inicioY,
            NEGRO);

    }
}

void dibujarDiscos(int torres[3][3], int numeroDiscos, int ancho, int separacion, int grosor){
    int inicioX = (ANCHO-(ancho*3)-(separacion*2))/2;

    int inicioY = ALTO-100;
    for(int i = 0; i<3; i++){
        for(int j = 0; j < numeroDiscos; j++){
            if(torres[i][j] != 0){
                int centroDisco = inicioX + ((ancho+separacion)*i) + (ancho/2);

                al_draw_filled_rectangle(
                centroDisco-20-(torres[i][j]*10),
                inicioY-(j*grosor*0.70),
                centroDisco+20+(torres[i][j]*10),
                inicioY-((j+1)*grosor*0.7),
                obtenerColor(torres[i][j])
                );
            }
        }
    }
}

void dibujarCursor(int cursor[], int alto, int ancho, int separacion, int grosor){
    int coordY = ALTO-alto-180;
    int inicioX = (ANCHO-(ancho*3)-(separacion*2))/2;
    for(int i = 0; i<3; i++){
        if(cursor[i]==-1){
            al_draw_filled_circle(
                inicioX + ((ancho+separacion)*i) + (ancho/2),
                coordY,
                10, NEGRO
            );
        }else if(cursor[i]!=0){
            al_draw_filled_rectangle(
                inicioX + ((ancho+separacion)*i) + (ancho/2)-20-(cursor[i]*10),
                coordY-(grosor*0.70)/2,
                inicioX + ((ancho+separacion)*i) + (ancho/2)+20+(cursor[i]*10),
                coordY+(grosor*0.70)/2,
                obtenerColor(cursor[i]));
        }
    }

}

ALLEGRO_COLOR obtenerColor(int color){
    switch(color){
    case 1:
        return VERDE;
        break;
    case 2:
        return ROJO;
        break;
    case 3:
        return AZUL;
        break;
    case 4:
        return NARANJA;
        break;
    case 5:
        return GRIS;
        break;
    default:
        return NEGRO;
            break;
    }

Descargas

Proyecto de Code::Blocks: HanoiAllegro.zip
Juego pre-compilado: HanoiCompilado.zip

Agradecimientos

Este proyecto fue posible gracias a los tutoriales y recursos de @YoshiFanGM y a la documentación de Allegro que está disponible en internet. Específicamente estos dos enlaces:
Tutorial - Programación de juegos en C++ y Allegro (del cual tomé prestado código para usar las flechas y algunos detalles más que tiene ahí)
Allegro 5 reference manual
 

YoshiFanGM

Gimmick Master
Colaborador
Mensajes
1.065
Calificaciones
498 3
Puntos
160
Ubicación
MTY
#2
Recuerdo haber visto ese mismo juego en alguna parte, pero no sabía su nombre...

Lo probé en Linux compilando el código, pero el teclado tardaba más en responder conforme pasaba el tiempo (para mover el cursor), pero pude encontrar la solución:

Antes de la condición if (redraw){, en la línea 157 asignas redraw = 1, pero no compruebas si el timer ha generado un evento. En resumen, solo hay que cambiar esa línea por esto:
Código:
if (event.type == ALLEGRO_EVENT_TIMER)
   redraw = 1;
(Aún así me sorprende que en Windows no tenga esa falla sin cambiar el código thinking_face)

Y si quieres organizar un poco más el código podrías separar las funciones de dibujar* en otro archivo .cpp, y usar un header .h donde solo estén las declaraciones de las funciones. Algo así:
C++:
void dibujarBases(int, int, int, int);
void dibujarDiscos(int [][3], int, int, int, int);
void dibujarCursor(int[], int, int, int, int);
ALLEGRO_COLOR obtenerColor(int);
C++:
#include "funciones.h"

void dibujarBases(int ancho, int grosor, int alto, int separacion){
    ...
}

void dibujarDiscos(int torres[3][3], int numeroDiscos, int ancho, int separacion, int grosor){
    ...
}

void dibujarCursor(int cursor[], int alto, int ancho, int separacion, int grosor){
    ...
}

ALLEGRO_COLOR obtenerColor(int color){
    ...
}

Pero en fin, me alegra ver que haya otro juego más hecho en Allegro y C++, aunque sea muy sencillo, seguramente ayudará a muchos usuarios que buscan el código para este juego de Hanoi. Buen trabajo, se agradece el aporte smiling_face_with_open_mouth_and_smiling_eyes
 
Última edición:
OP

BBR~

Un héroe de otoño
Moderador
Editor
Mensajes
10.487
Calificaciones
3K 35
Puntos
625
Ubicación
CDMX
#3
Lo probé en Linux compilando el código, pero el teclado tardaba más en responder conforme pasaba el tiempo (para mover el cursor), pero pude encontrar la solución:

Antes de la condición if (redraw){, en la línea 157 asignas redraw = 1, pero no compruebas si el timer ha generado un evento. En resumen, solo hay que cambiar esa línea por esto:
Código:
if (event.type == ALLEGRO_EVENT_TIMER)
   redraw = 1;
(Aún así me sorprende que en Windows no tenga esa falla sin cambiar el código thinking_face)
Y eso además solucionó la precisión del contador de tiempo dentro (antes estaba como un segundo adelante del tiempo real de ejecución). Muchas gracias. :)

Y si quieres organizar un poco más el código podrías separar las funciones de dibujar* en otro archivo .cpp, y usar un header .h donde solo estén las declaraciones de las funciones. Algo así:
C++:
void dibujarBases(int, int, int, int);
void dibujarDiscos(int [][3], int, int, int, int);
void dibujarCursor(int[], int, int, int, int);
ALLEGRO_COLOR obtenerColor(int);
C++:
#include "funciones.h"

void dibujarBases(int ancho, int grosor, int alto, int separacion){
    ...
}

void dibujarDiscos(int torres[3][3], int numeroDiscos, int ancho, int separacion, int grosor){
    ...
}

void dibujarCursor(int cursor[], int alto, int ancho, int separacion, int grosor){
    ...
}

ALLEGRO_COLOR obtenerColor(int color){
    ...
}
Justo esto es parte de lo que quiero hacer. Estoy completamente seguro que hay más desorden ahí dentro que puedo arreglar también.

Pero en fin, me agrada ver que haya otro juego más hecho en Allegro y C++, aunque sea muy sencillo, seguramente ayudará a muchos usuarios que buscan el código para este juego de Hanoi. Buen trabajo, se agradece el aporte smiling_face_with_open_mouth_and_smiling_eyes
Gracias a ti por proporcionar tan buena información. De verdad que si no fuera por tus posts jamás me hubiera animado ni a intentar esto y mucho menos habría indagado en la creación de videojuegos.
 
Arriba Pie