Intro a la programación de juegos

Por Nacho Cabanes

Contenido Indice Cambios Enlaces Autor

5. El juego del Ahorcado.

Contenido de este apartado:


5.1. Pautas generales.

La idea básica de lo que tiene que hacer el juego es la siguiente:

  • Generar un número al azar, y con ese número escoger una palabra de entre las predefinidas. Esa es la palabra que deberá adivinar el usuario.
  • Repetimos:
    • El usuario elige una letra.
    • Si esa letra está en la palabra buscada, mostramos en que posiciones se encuentra.
    • Si no está en la palabra, le queda un intento menos.
  • Hasta que se quede sin intentos o acierte la palabra.
El resto de características ya son "refinamientos". Por ejemplo, si termina sin acertar, le decimos cual era la palabra buscada. Además, por tratarse de un juego gráfico, en cada "pasada" comprobaremos cuantos fallos lleva, para dibujar la correspondiente parte del "patíbulo" de nuestro ahorcado.

Y todavía no buscamos una gran presentación, nos basta con que la apariencia sea sencilla, algo así:

Por tanto, la única complicación es: escribir texto en pantalla, dibujar líneas y círculos, y generar números al azar. Todo eso ya lo samos hacer, así que vamos con ello...


5.2 Ahorcado en C. 

Debería ser fácil de seguir...
 
/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*    IPJ05C.C                  */
/*                              */
/*  Quinto ejemplo: juego del   */
/*  ahorcado (versión básica)   */
/*                              */
/*  Comprobado con:             */
/*  - Djgpp 2.03 (gcc 3.2)      */
/*    y Allegro 4.02 - MsDos    */
/*  - MinGW 2.0.0-3 (gcc 3.2)   */
/*    y Allegro 4.02 - Win 98   */
/*  - DevC++ 4.9.9.2(gcc 3.4.2) */
/*    y Allegro 4.03 - Win XP   */
/*------------------------------*/
 

#include <stdio.h>      /* Rutinas estándar, como "printf" */
#include <string.h>     /* Manejo de cadenas */
#include <stdlib.h>     /* Para "rand" */
#include <time.h>       /* Para "time" */
#include <ctype.h>      /* Para "tolower" */
#include <allegro.h>
 

#define NUMPALABRAS 10
#define MAXINTENTOS 5
  /* No deberíamos modificar el número máximo de intentos,
     porque vamos a dibujar 5 "cosas" cuando se equivoque" */

char palabra[80], intento[80], letras[80];
           /* La palabra a adivinar, la que */
           /* el jugador 2 va consiguiendo y */
           /* las letras que se han probado */

int oportunidades;        /* El número de intentos permitido */
char letra;               /* Cada letra que prueba el jug. dos */
int i;                    /* Para mirar cada letra, con "for" */
int acertado;             /* Si ha acertado alguna letra */
char ficticia[2];         /* Aux, para añadir letra a cadena */

char mensaje[80];            /* Los letreros que mostrar en pantalla */

char datosPalabras [NUMPALABRAS][80]=
    {
    "Alicante","Barcelona","Guadalajara","Madrid",
    "Toledo","Malaga","Zaragoza","Sevilla",
    "Valencia","Valladolid"
    };
 

 void PrimerFallo()   /* Primer fallo: */
 {                    /* Dibujamos la "plataforma" */
   line(screen,20,180,120,180, palette_color[13]);
 }
 

 void SegundoFallo()   /* Segundo fallo: */
 {                     /* Dibujamos el "palo vertical" */
   line(screen,100,180,100,125, palette_color[13]);
 }
 

 void TercerFallo()   /* Tercer fallo: */
 {                    /* Dibujamos el "palo superior" */
   line(screen,100,125,70,125, palette_color[13]);
 }

 void CuartoFallo()   /* Cuarto fallo: */
 {                    /* Dibujamos la "cuerda" */
   line(screen,70,125,70,130, palette_color[13]);
 }
 

 void QuintoFallo()   /* Quinto fallo: */
 {
 int j;               /* Dibujamos la "persona" */

        /* Cabeza */
   circle(screen,70,138,8, palette_color[12]);
        /* Tronco */
   line(screen,70,146,70,160, palette_color[12]);
        /* Brazos */
   line(screen,50,150,90,150, palette_color[12]);
        /* Piernas */
   line(screen,70,160,60,175, palette_color[12]);
   line(screen,70,160,80,175, palette_color[12]);
 }
 
 

int main()
{

  allegro_init();          /* Inicializamos Allegro */
  install_keyboard();
 

                           /* Intentamos entrar a modo grafico */
  if (set_gfx_mode(GFX_SAFE,320,200,0,0)!=0){
    set_gfx_mode(GFX_TEXT,0,0,0,0);
    allegro_message(
      "Incapaz de entrar a modo grafico\n%s\n",
      allegro_error);
    return 1;
  }

                            /* Si todo ha ido bien: empezamos */
  srand(time(0));                 /* Valores iniciales */
  strcpy(palabra, datosPalabras[ rand()%(NUMPALABRAS+1)]);
  oportunidades = MAXINTENTOS;

  strcpy(letras,"");

          /* Relleno con _ y " " lo que ve Jug. 2 */
  for (i=1; i<=strlen(palabra); i++)
    if (palabra[i-1]==' ' )
      intento[i-1]=' ';
    else
      intento[i-1]='_';
  intento[i]='\0';       /* Y aseguro que termine correctamente */
 

           /* Parte repetitiva: */
  do {
    clear_bitmap(screen);
 

    /* Dibujo lo que corresponde del "patibulo" */
    if (oportunidades <=4) PrimerFallo();
    if (oportunidades <=3) SegundoFallo();
    if (oportunidades <=2) TercerFallo();
    if (oportunidades <=1) CuartoFallo();

                           /* Digo cuantos intentos le quedan */
    textprintf(screen, font, 80,18, palette_color[15],
      "Te quedan %d intentos", oportunidades);
 

                               /* Le muestro c¢mo va */
    textprintf(screen, font, 80,32, palette_color[15],
      intento, oportunidades);

                               /* Las letras intentadas */
    textprintf(screen, font, 20,72, palette_color[14],
      "Letras intentadas: %s", letras);

                              /* Y le pido otra letra */
    textprintf(screen, font, 20,60, palette_color[14],
      "Que letra?");

    letra = readkey()&0xff;

           /* Añado esa letra a las tecleadas*/
    strcpy (ficticia,"a");  /* Usando una cadena de texto aux */
    ficticia[0]= letra;
    strcat (letras, ficticia);

    acertado = 0;       /* Miro a ver si ha acertado */
    for (i=1; i<=strlen(palabra); i++)
      if(tolower(letra)== tolower(palabra[i-1]))
  {
  intento[i-1]= palabra[i-1];
  acertado = 1;
  }
 

    if (! acertado )    /* Si falló, le queda un intento menos */
      oportunidades --;

  }
  while ( strcmp (intento,palabra)    /* Hasta que acierte */
    && (oportunidades>0));    /* o gaste sus oportunidades */
 

                            /* Le felicito o le digo cual era */

  if ( strcmp (intento,palabra)==0)
    textprintf(screen, font, 20,100, palette_color[11],
      "Acertaste!");
  else
    {
    textprintf(screen, font, 20,100, palette_color[11],
      "Lo siento.  Era: %s", palabra);
    QuintoFallo();
    }
 

  readkey();
  return 0;

}

                 /* Termino con la "macro" que me pide Allegro */
END_OF_MAIN();




5.3. Ahorcado en Pascal.

Esta versión debería ser aun más fácil de seguir que la de C:
 
(*----------------------------*)
(*  Intro a la programac de   *)
(*  juegos, por Nacho Cabanes *)
(*                            *)
(*    IPJ05P.PAS              *)
(*                            *)
(*  Cuarto ejemplo: juego del *)
(*  ahorcado (versión básica) *)
(*                            *)
(*  Comprobado con:           *)
(*  - FreePascal 1.10 - Dos   *)
(* - FreePascal 2.0 -Windows *)
(*----------------------------*)

uses graph, crt;
(* Cambiar por "uses wincrt, ..." bajo Windows *)

const NUMPALABRAS = 10;
const MAXINTENTOS = 5;
  (* No deberíamos modificar el número máximo de intentos,
     porque vamos a dibujar 5 'cosas' cuando se equivoque *)

var
  palabra, intento, letras: string[80];
            (* La palabra a adivinar, la que *)
            (* el jugador 2 va consiguiendo y *)
            (* las letras que se han probado *)

  oportunidades: integer;     (* El número de intentos permitido *)
  oportStr: string;           (* Y para convertirlo a cadena *)
  letra: char;                (* Cada letra que prueba el jug. dos *)
  i: integer;                 (* Para mirar cada letra, con 'for' *)
  acertado: boolean;          (* Si ha acertado alguna letra *)

  mensaje: string[80];        (* Los letreros que mostar en pantalla *)

const datosPalabras: array [1..NUMPALABRAS] of string= (
    'Alicante','Barcelona','Guadalajara','Madrid',
    'Toledo','Malaga','Zaragoza','Sevilla',
    'Valencia','Valladolid');


 procedure PrimerFallo;       (* Primer fallo: *)
 begin                        (* Dibujamos la 'plataforma' *)
   setcolor(13);
   line(20, 180,  120, 180);
 end;


 procedure SegundoFallo;       (* Segundo fallo: *)
 begin                         (* Dibujamos el 'palo vertical' *)
    setcolor(13);
   line(100, 180,  100, 125);
 end;


 procedure TercerFallo;       (* Tercer fallo: *)
 begin                        (* Dibujamos el 'palo superior' *)
   setcolor(13);
   line(100, 125,   70, 125);
 end;

 procedure CuartoFallo;       (* Cuarto fallo: *)
 begin                        (* Dibujamos la 'cuerda' *)
   setcolor(13);
   line(70, 125,   70, 130);
 end;


 procedure QuintoFallo;       (* Quinto fallo: *)
 begin
                              (* Dibujamos la 'persona' *)
   setcolor(12);
        (* Cabeza *)
   circle(70, 138,   8);
        (* Tronco *)
   line(70, 146,   70, 160);
        (* Brazos *)
   line(50, 150,  90, 150);
        (* Piernas *)
   line(70, 160,   60, 175);
   line(70, 160,   80, 175);
 end;




var
  gd,gm, error : integer;


BEGIN
  gd := D8bit;
  gm := m320x200;
(* Si falla bajo Windows, probar gd:=0; gm:=0; *)
  initgraph(gd, gm, '');

                               (* Intentamos entrar a modo grafico *)
  error := graphResult;
  if error <> grOk then
    begin
    writeLn('No se pudo entrar a modo grafico');
    writeLn('Error encontrado: '+ graphErrorMsg(error) );
    halt(1);
    end;


                             (* Si todo ha ido bien: empezamos *)
  randomize;                 (* Valores iniciales *)
  palabra := datosPalabras[ round(random * NUMPALABRAS) + 1 ];
  oportunidades := MAXINTENTOS;

  letras := '';

           (* Relleno con _ y ' ' lo que ve Jug. 2 *)
  intento := palabra;
  for i:=1 to length(palabra) do
    if palabra[i] = ' ' then
      intento[i] := ' '
    else
      intento[i] := '_';

            (* Parte repetitiva: *)
  repeat
    clearDevice;

    (* Dibujo lo que corresponde del 'patibulo' *)
    if (oportunidades <= 4) then PrimerFallo;
    if (oportunidades <= 3) then SegundoFallo;
    if (oportunidades <= 2) then TercerFallo;
    if (oportunidades <= 1) then CuartoFallo;

                               (* Digo cuantos intentos le quedan *)
    setColor(15);
    str(oportunidades, oportStr);
    outTextXY(80, 18, 'Te quedan '+oportStr+' intentos');


                                (* Las letras intentadas *)
    outTextXY(80, 32, intento);

    setColor(14);
    outTextXY(20, 72, 'Letras intentadas: '+letras);

                                 (* Y le pido otra letra *)
    outTextXY(20, 60, 'Que letra?');

    letra := readkey;

            (* Añado esa letra a las tecleadas*)
    letras := letras + letra;

    acertado := false;           (* Miro a ver si ha acertado *)
    for i := 1 to length(palabra) do
      if lowercase(letra) = lowercase(palabra[i]) then
        begin
        intento[i] := palabra[i];
        acertado := true;
        end;


    if not acertado then    (* Si falló, le queda un intento menos *)
      oportunidades := oportunidades - 1;

  until  (intento = palabra)   (* Hasta que acierte *)
    or (oportunidades=0);      (* o gaste sus oportunidades *)


                               (* Le felicito o le digo cual era *)



  setcolor(15);
  if intento = palabra then
    outTextXY(20, 100, 'Acertaste!')
  else
    begin
    outTextXY(20, 100, 'Lo siento.  Era: '+ palabra);
    QuintoFallo;
    end;


  readkey;
  closeGraph;

end.


5.4. Ahorcado con Java.

La diferencia grande con la versión en C es la forma en que se estructura, como ya vimos en el ejemplo anterior: en "init" pondremos todo lo que sea inicialización, en "paint" todo lo que dibuje en pantalla (aunque hemos desglosado ligeramente lo que hacer en el caso de cada fallo, igual que en la versión en C), y en "keyPressed" agruparemos las operaciones básicas que hay que realizar cuando se pulsa una tecla.

  • Las demás diferencias son básicamente de sintaxis. Por ejemplo:
    • Para escribir un texto y una cifra usamos cosas como expresiones más parecidas a Pascal que a C, cosas como g.drawString("Te quedan " + oportunidades + " intentos", 80, 18);
    • Para leer el carácter que hay en una posición de una cadena usaremos palabra.charAt(i-1) y para cambiar el carácter que hay en una posición se haría con palabra.setCharAt(i-1, 'a').
    • Para comparar dos cadenas de texto usamos la construcción palabra.equals(otraPalabra)
    • La cadena de texto que debemos modificar con frecuencia (intento) no la definimos como String sino como StringBuffer, que es el tipo de datos que permite operaciones avanzadas como SetCharAt.

La apariencia será algo como:


Y una forma de desarrollarlo sería:
 

/*----------------------------*/ 
/*  Intro a la programac de   */ 
/*  juegos, por Nacho Cabanes */ 
/*                            */ 
/*    ipj05j.java             */ 
/*                            */ 
/*  Cuarto ejemplo: juego del */ 
/*  ahorcado (versión básica) */ 
/*                            */ 
/*  Comprobado con:           */ 
/*  - JDK 1.4.2_01            */  
/*  - JDK 1.5.0               */  
/*----------------------------*/ 

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

 
public class ipj04j extends Applet 
    implements KeyListener 
{

    final int NUMPALABRAS = 10;
    final int MAXINTENTOS = 5;
      // No deberíamos modificar el número máximo de intentos,
      // porque vamos a dibujar 5 "cosas" cuando se equivoque
    
    String palabra;        // La palabra a adivinar
    StringBuffer intento;  // Lo que el jugador 2 va consiguiendo
    String letras="";      // Las letras que se han probado
    
    int oportunidades;     // El número de intentos permitido
    char letra;            // Cada letra que prueba el jug. dos
    int i;                 // Para mirar cada letra, con "for"
    boolean acertado;      // Si ha acertado alguna letra
    boolean terminado;     // Si la partida ha terminado
    
    String datosPalabras []=
    {
        "Alicante","Barcelona","Guadalajara","Madrid",
        "Toledo","Malaga","Zaragoza","Sevilla",
        "Valencia","Valladolid"
    };


    void PrimerFallo(Graphics g)
    {   // Primer fallo: Dibujamos la "plataforma"
        g.setColor(Color.cyan);
        g.drawLine(20, 180,  120, 180);
    }
    
    
    void SegundoFallo(Graphics g)
    {   // Segundo fallo: Dibujamos el "palo vertical"
        g.drawLine(100, 180,  100, 125);
    }
    
    
    void TercerFallo(Graphics g)
    {   // Tercer fallo: Dibujamos el "palo superior"
        g.drawLine(100, 125,   70, 125);
    }
    
    
    void CuartoFallo(Graphics g)
    {  // Cuarto fallo: Dibujamos la "cuerda"
       g.drawLine(70, 125,   70, 130);
    }
    
    
    void QuintoFallo(Graphics g)
    {  // Quinto fallo: Dibujamos la "persona"
    int j;
    
            // Cabeza
        g.setColor(Color.yellow); 
        g.drawOval(62, 130, 16, 16);
            // Tronco
        g.drawLine(70, 146,   70, 160);
            // Brazos
        g.drawLine(50, 150,  90, 150);
            // Piernas
        g.drawLine(70, 160,   60, 175);
        g.drawLine(70, 160,   80, 175);
    }
    

    public void init() {

        // Valores iniciales
        i = (int) Math.round(Math.random() * NUMPALABRAS);
        palabra = datosPalabras[ i ];
        oportunidades = MAXINTENTOS;
    
        // Relleno con * y " " lo que ve Jug. 2
        intento = new StringBuffer(palabra);
            
        for (i=1; i<=palabra.length(); i++)
        if (palabra.charAt(i-1) == ' ' )
            intento.setCharAt(i-1, ' ');
        else
            intento.setCharAt(i-1, '*');
        
        terminado = false;
        requestFocus();
        addKeyListener(this);
    }

    
    public void paint(Graphics g) {

        // Primero borro el fondo en negro
        g.setColor( Color.black );
        g.fillRect( 0, 0, 639, 479 );
        
        // Digo cuantos intentos le quedan
        g.setColor(Color.white);
        g.drawString("Te quedan " + oportunidades + " intentos", 
            80, 18);
    
        // Le muestro como va
        g.drawString(intento.toString(), 
            80, 32);    
    
        // Muestro las letras probadas
        g.setColor(Color.yellow);
        g.drawString("Letras intentadas:" + letras, 
            20, 72);    
    
        // Y le pido otra letra
        g.drawString("Que letra?", 20, 60);    

        // Dibujo lo que corresponde del "patibulo"
        if (oportunidades <= 4) PrimerFallo(g);
        if (oportunidades <= 3) SegundoFallo(g);
        if (oportunidades <= 2) TercerFallo(g);
        if (oportunidades <= 1) CuartoFallo(g);
        // Si se acabo: Le felicito o le digo cual era
        if ((oportunidades <= 0) || (palabra.equals(intento.toString()))) {
            terminado = true;
            if ( palabra.equals(intento.toString() ) )
                g.drawString("Acertaste!", 
                    20, 100);    
            else
            {
            g.drawString("Lo siento.  Era: " + palabra, 
                20, 100);        
            QuintoFallo(g);
            }
        }
    } 


    public void keyTyped(KeyEvent e) {
        letra=e.getKeyChar();
        if (! terminado) {
            letras=letras+letra;
              
            acertado = false;      // Miro a ver si ha acertado
            for (i=1; i<=palabra.length(); i++)
              if (Character.toLowerCase(letra) == Character.toLowerCase(palabra.charAt(i-1)))
                {
                intento.setCharAt(i-1, palabra.charAt(i-1) );
                acertado = true;
                }
        
            if ( ! acertado )     // Si falló, le queda un intento menos
              oportunidades --;            
        }
        repaint();
        e.consume();
    }
    
    public void keyReleased(KeyEvent e) {
    }
 
    public void keyPressed(KeyEvent e) {
    }
        
} 




Contenido Indice Cambios Enlaces Autor
 
Nacho Cabanes, 2005
Última versión en www.pobox.com/users/ncabanes