lunes, 10 de octubre de 2016

Crear un clon del Ahorcado en C


Español


Primero quiero aclarar que la 3era parte del proyecto de Generación Procedural de Circuitos de Carrera esta por salir. Todavía no funciona del todo pero empieza a dar resultados de a poco como el siguiente:


Pero ahora les traigo un trabajo práctico que tuve que hacer para una materia de programación. 

Vamos a recrear el juego del Ahorcado escrito en ANSI C, totalmente portable para Windows o Linux. (Nota: el juego va a ser desarrollado para Linux).

Las herramientas a usar son:
  •  Sublime Text
  • Compilador GCC

Los encabezados de las librerías que vamos a usar en todo el proyecto son:
  • stdio.h
  • stlib.h
  • time.h
  • string.h
  • ctype.h
Todo pertenecen a la librería estándar.


 Primero vamos a dividir en pasos que es lo que le programa va a hacer:
  1. Dibujar la pantalla inicial, la que va a contener al dibujo del ahorcado.
  2. Elegir de una lista de palabras una, de forma aleatoria.
  3. Pedir al usuario que introduzca una letra.
  4. Verificar si la letra pertenece o no a la palabra secreta, en caso de que si, reemplazar los espacios en blanco de la palabra secreta con la letra. Si no, dibujar una nueva parte del ahorcado.
  5. Repetir 4 hasta que se adivine la palabra o se haya dibujado a todo el ahorcado.
Para el dibujo del ahorcado lo que quería hacer es utilizar símbolos ASCII para dibujar como sería el peor caso:

/*    MATRIZ DIBUJO
   
          |
          |    Bye
          []
        --|--
          |     r
         / \    |
     ----------
   /            \

*/

Primero debemos dibujar la "MATRIZ DIBUJO" pero sin la persona. En mejor caso (que el jugador acierte todas las letras), esta no cambiaría.

ALTO y ANCHO son 2 constantes simbólicas que las calculé de acuerdo a cuantos caracteres ocupaba la matriz dibujo que hice arriba.

void
Dibujar(int m[ALTO][ANCHO]){
    for(int i = 0; i < ALTO_DIBUJO; i++){
        for(int j = 0; j < ANCHO_DIBUJO; j++){
            if( (i==0 || i==1) && j==6){
                m[i][j] = '|';
            }      
            else if(i==7 && j!=0 && j!= ANCHO-1){
                m[i][j] = '-';
            }
            else if(i==8 && j==0){
                m[i][j] = '/';
            }
            else if(i==8 && j==ANCHO-1){
                m[i][j] = '\\';
            }
            else if( i==4 && j==10){
                m[i][j] = 'r';
            }
            else if( (i==5 || i==6) && j==10){
                m[i][j] = '|';
            }
            else{
                m[i][j] = ' ';
            }
        }
    }
}
Se podría haber simplificado y simplemente en vez de tener los else if poner los correspondientes m[i][j] para cada caracter especial.

Para todos los demás caracteres que no forman parte del dibujo, los rellené con espacios.

Ahora vamos a la función errar.

A esta función se la llama si el jugador no acertó la letra. Se encarga de aumentar un contador de error "cant_err" y dependiendo de cuanto valga esta variable, dibujar una parte del ahorcado.

void
errar(int * cant_err, int m[ALTO][ANCHO]){
    (*cant_err)++;
    switch(*cant_err){
        case 1:
                m[2][6] = '[';
                m[2][7] = ']';
                break;
        case 2:
                m[3][6] = '|';
                m[4][6] = '|';
                break;
        case 3:
                m[3][7] = '-';
                m[3][8] = '-';
                break;
        case 4:
                m[3][5] = '-';
                m[3][4] = '-';
                break;
        case 5:
                m[5][5] = '/';
                break;
        case 6:
                m[5][7] = '\\';
                m[1][9] = 'B';
                m[1][10] = 'y';
                m[1][11] = 'e';
                m[6][10] = '/';
                m[5][11] = '/';
                m[5][10] = ' ';
                m[4][12] = 'r';
                m[4][10] = ' ';
                break;
    }
}

Ahora vamos con la función que verifica si la letra que ingresamos pertenece o no a la palabra:

int
verificar_letra(char * palabra_secreta, char letra, char * vec_resp){
    int flag=0;
    for(int i = 0; palabra_secreta[i]!=0; i++){
        if(palabra_secreta[i] == letra){
            vec_resp[i] = letra;
            flag=1;
        }
    }
    return flag;
}

Como podrán ver, esta función nos retorna 1 si la letra estaba en la palabra secreta y 0 si no lo estaba. Además, le asigna la letra a un vector llamado "vec_resp".

El vector "vec_resp" es un vector que tiene la misma dimensión que la palabra secreta que se elija, su función es llenarse con "*" para mostrárselo al usuario y que, si este acierta, se reemplace con la letra correspondiente.

Para crear este vector utilizamos la función "Crear_vector_Respuesta":

char *
Crear_vec_respuesta(char * palabra_secreta){
    int i=0, j;
    char *p;
    while(palabra_secreta[i]){
        i++;
    }
    p = malloc(i);
    for( j =0 ; j< i;j++){
        p[j] = '*';
    }
    p[j] = 0;
    return p;
}

La mejor forma de hacerlo es creando un vector de forma dinámica, pues no sabemos la longitud de la palabra que se va a elegir aleatoriamente. Entonces la función calcula la longitud de la palabra que se eligió y crea un vector de esa dimensión lleno de "*".

Ahora lo que quiero hacer es dibujar en cada paso la "MATRIZ DIBUJO". Si el jugador falla, la función "errar" cambiará la matriz pero esta va a simplemente imprimirla. Esta función también va a imprimir al "vector respuesta".

void
Imprimir_pantalla(int m[ALTO][ANCHO] char *vec_resp, char *usadas){
    LIMPIAR_PANTALLA;
    printf("\n");
    for(int i = 0; i < ALTO; i++){
        printf("\t\t");
        for(int j = 0; j < ANCHO; j++){
            printf("%c", m[i][j]);
        }
        printf("\n");
    }
    printf("\n\n");
    printf("\t    \t");
    for(int i = 0; vec_resp[i] != 0 ; i++){
        printf("%c ", vec_resp[i]);
    }
    printf("\t    \t \n");
    printf("\t Usadas: ");
    for(int i = 0; usadas[i] != 0 ; i++){
        printf("%c ", usadas[i]);
    }

    printf("\n");
    LIMPIAR_PANTALLA;
}

 Donde  LIMPIAR_PANTALLA es una macro que simplemente contiene un printf con varios "\n" dentro.

Esta función lo que hace es recorrer todos los vectores e imprimir sus valores como caracteres. La tabulación es simplemente por presentación. 

Lo importante de usar tantos espacios en blanco es para que, al imprimir la pantalla luego de que el jugador introdujo una letra, no pueda ver los "restos"  de las impresiones previas.

Lo último de esta función también utiliza un vector llamado "usadas", este vector es simplemente un vector de longitud 6 (lo máximo que puedo fallar es 6 veces, cuando se dibujó todo) en donde se irán mostrando las letras incorrectas que ya usamos.

Finalmente, la función main se encarga de inicializar los vectores y de controlar el ciclo principal del juego.

int
main(void){
int Matriz_Dibujo[ALTO][ANCHO], cant_err=0, k, w_used=0, h=0;
char lett;
char *palabras[5]={"ARBOL", "CASA", "AUTO", "JUEGOS", "COMPUTADORA"};
char * palabra_secreta, *vec_resp, usadas[7];
    srand(time(NULL));
    while(usadas[h]!=0){
        usadas[h]=0;
        h++;
    }
    palabra_secreta = palabras[rand()%5];
    Dibujar(Matriz_Dibujo);
    vec_resp = Crear_vec_respuesta(palabra_secreta);
    Imprimir_pantalla(Matriz_Dibujo, vec_resp, usadas);
    do{
        lett = toupper(getchar());
        getchar(); //para quitar el \n //IMPORTANTISIMO
        k = verificar_letra(palabra_secreta, lett, vec_resp);
        if(k){
            Imprimir_pantalla(Matriz_Dibujo,vec_resp, usadas);
        }
        else{
            usadas[w_used++] = lett;
            errar(&cant_err, Matriz_Dibujo);
            Imprimir_pantalla(Matriz_Dibujo, vec_resp, usadas);
        }
    }while((lett='\n') && (cant_err != 6) && (strcmp(palabra_secreta,vec_resp)!=0) );
    if(cant_err==6){
        printf("You Lose! the answer was %s \n", palabra_secreta);
    }
    if((strcmp(palabra_secreta,vec_resp)==0)){
        printf("You Win!!! \n");
    }
    return 0;
}

Primero definimos las variables que vamos a utilizar, puse también uno algunos ejemplos de palabras secretas para probar como funciona.

Después, inicializamos al vector "usadas" con todos ceros. Se podría haber hecho en otra función para mantener mejor estilo.

Luego, elejimos la palabra secreta siendo esta un elemento random de nuestro vector "palabras" y llamamos a la función Dibujar para crear el "setting" inicial para nuestro juego.

Creamos nuestro vector respuesta utilizando la función "crear_vec_respuesta" e imprimimos por primera vez la pantalla.

A partir de acá comienza el ciclo.

Se le va a pedir al usuario un caracter. Se va a utilizar la función verificar y a partir de ahí se va a determinar si se reescribe nuestro vector "respuesta", indicando que acertamos o si se modifica el vector "usadas" y se modifica la "matriz dibujo", indicando que erramos.

El ciclo se va a repetir mientras el caracter ingresado no sea un "enter", mientras la cantidad de errores no sea 6 (nuestro máximo) y mientras la palabra secreta sea distinta a la palabra secreta (utilizando la función string compare (strcmp)).

Luego, los if están para indicar si salimos del ciclo por que perdimos o por que ganamos.

Lo más importante del ciclo es: luego de pedirle al jugador un caracter con getchar(), hacer OTRO getchar(), para consumir el "\n" (el enter) del buffer, ya que si no, el ciclo se va a volver a ejecutar haciendo de cuenta que el caracter que ingresó el usuario es un enter.


Y eso es todo. 

Si quieren, pueden agregarle más palabras al conjunto de "palabras" para que sea más entretenido (y después cambiar el rand()%"nueva_cant_de_palabras" de la 10° línea del main).



Cualquier duda o sugerencia para optimizar pueden dejarla en los comentarios.

-L