4. Cómo generar números al azar. Un primer juego: adivinar números
Contenido de este apartado:
4.1. Pautas generales.
Un requisito fundamental en la mayoría de los juegos es que no sea siempre igual, para que no sea predecible. Si hay cosas al azar, no bastará con que el jugador memorice, sino que tendrá que enfrentarse a retos que no serán siempre los mismos.Por eso, vamos a ver cómo generar números al azar. A partir de esos números, haremos el que posiblemente es el juego más sencillo posible: adivinar un número oculto, teniendo una cantidad limitada de intentos.
En cuanto a obtener números aleatorios al azar, suele haber ciertas cosas comunes en casi cualquier lenguaje de programación: las funciones que nos dan ese número se llamarán "rand" o algo muy parecido, porque "aleatorio" en inglés es "random". Por otra parte, en muchos lenguajes es necesario decirle una "semilla" a partir de la que empezar a generar los números. Si la semilla fuera siempre la misma, los números obtenidos, por lo que se suele usar como semilla el reloj interno del ordenador, porque sería casi imposible que dos partidas comenzaran exactamente a la misma hora (con una precisión de centésimas de segundo o más). Para complicar la situación, en algunos lenguajes la función "rand" (o como se llame) nos da un número entre 0 y 1, que nosotros deberíamos multiplicar si queremos números más grandes (eso pasa en Pascal y Java); en otros lenguajes obtenemos un número muy grande, entre 0 y otro valor (a veces cerca de 32.000, otras cerca de 2.000 millones, según la biblioteca que usemos), como es el caso de C, así que para obtener un número más pequeño lo que haremos es dividir y quedarnos con el resto de esa división.
Así, en nuestro caso, en la versión de C del juego haremos (semilla a partir del reloj, y un número enorme, del que tomamos el resto de la división):
srand(time(0));
numeroAdivinar = rand() % MAXIMONUMERO;
y en Pascal algo como (primero la semilla y luego un número entre 0 y 1, que multiplicamos)
randomize;
numeroAdivinar := round(random * MAXIMONUMERO);
y en Java (similar a Pascal, pero sin necesidad de pedir que se cree la semilla):
numeroAdivinar = (int) Math.round(Math.random() * MAXIMONUMERO);
Por lo que respecta a nuestro juego, la idea de lo que tiene que hacer (lo que se suele llamar "pseudocódigo") podría ser algo así:
Generar un número al azar entre 0 y 99
acertado = FALSO
repetir
pedir número al usuario
si el número tecleado el número al azar, terminado = VERDADERO
en caso contrario, si el número tecleado es más pequeño, avisar
en caso contrario, si el número tecleado es mayor, avisar
incrementar Numero de intentos
hasta que (acertado) o (Numero de intentos = maximo)
si acertado, felicitar
en caso contrario, decir qué número era el correcto
Pasar de aquí a la práctica no debería ser difícil, salvo quizá por el hecho de "pedir número al usuario". Como ya habíamos comentado, en general no tendremos en modo gráfico rutinas que permitan leer entradas complejas por teclado. Pero como esa es la única complicación de este juego, lo podemos evitar de una forma sencilla: obligaremos a que el usuario tenga que teclear un número de 2 cifras entre 00 y 99, de modo que nos bastará con dos órdenes de comprobar una pulsación de teclado (readkey, getch o similar).
Pasar de las dos letras "1" y "4" al número 14 tampoco es difícil. Algunos lenguajes nos permitirán "juntar" (concatenar, hablando más correctamente) las dos letras para obtener "14" y luego decir que calcule el valor numérico de este texto. Otra opción es restar la letra "0" a cada cifra, con lo cual ya tenemos el valor numérico de cada una, es decir 1 y 4; para obtener el 14 basta con multiplicar el primer número por 10 y sumarle el segundo. Este es el método que usaremos en C y en Pascal en este ejemplo, para no necesitar recurrir a bibliotecas externas de funciones para convertir los valores.
(Un consejo: intenta hacer
cada juego
tú
mismo antes de ver cómo lo he resuelto yo.
El
enfrentarte con los problemas y comparar con
otras
soluciones hará que aprendas mucho
más
que si te limitas a observar)
4.2 adivinar Números Con C y Allegro.
Va a ser muy poco más que seguir el pseudocódigo de antes, pero adaptado a la sintaxis del lenguaje C y dando alguna vuelta puntual en casos como el de leer lo que teclea el usuario, que ya hemos comentado:
/*----------------------------*/ /* Intro a la programac de */ /* juegos, por Nacho Cabanes */ /* */ /* ipj04c.c */ /* */ /* Cuarto ejemplo: adivinar */ /* un numero - modo grafico */ /* */ /* Comprobado con: */ /* - Dev-C++ 4.9.9.2 */ /* y Allegro 4.2.1 */ /*----------------------------*/ #include <allegro.h> int numeroAdivinar, numeroTecleado; char tecla1, tecla2; int acertado; int intentos; int lineaEscritura; #define MAXIMONUMERO 99 #define NUMEROINTENTOS 6 int main() { allegro_init(); install_keyboard(); 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; } intentos = 0; lineaEscritura = 50; srand(time(0)); numeroAdivinar = rand() % MAXIMONUMERO; acertado = 0; textout(screen, font, "Adivinar numeros", 10,10, palette_color[14]); do { textout(screen, font, "Teclea dos cifras (00 a 99)", 15,lineaEscritura, palette_color[13]); tecla1 = readkey(); textprintf(screen, font, 235,lineaEscritura, palette_color[13], "%c", tecla1); tecla2 = readkey(); textprintf(screen, font, 243,lineaEscritura, palette_color[13], "%c", tecla2); numeroTecleado = (int) (tecla1 - '0') * 10 + tecla2 - '0'; if (numeroTecleado == numeroAdivinar) acertado = 1; else if (numeroTecleado < numeroAdivinar) textout(screen, font, "Corto", 260,lineaEscritura, palette_color[12]); else if (numeroTecleado > numeroAdivinar) textout(screen, font, "Grande", 260,lineaEscritura, palette_color[12]); intentos++; lineaEscritura += 10; } while( (!acertado) && (intentos < NUMEROINTENTOS)); if (acertado) textout(screen, font, "Acertaste!!!", 160,180, palette_color[15]); else textprintf(screen, font, 160,180, palette_color[15], "Era: %d", numeroAdivinar); readkey(); } END_OF_MAIN();
que se vería así:
4.3. Adivinar números en Pascal.
El juego en Pascal es casi idéntico al de C (salvando la diferencia de sintaxis, claro):
(*----------------------------*) (* Intro a la programac de *) (* juegos, por Nacho Cabanes *) (* *) (* IPJ04P.PAS *) (* *) (* Cuarto ejemplo: adivinar *) (* un numero - modo grafico *) (* *) (* Comprobado con: *) (* - FreePascal 2.0.4 (WinXp) *) (*----------------------------*) uses wincrt, graph, sysUtils; (* Cambiar por "uses crt, ..." bajo Dos *) var gd,gm, error : integer; numeroAdivinar, numeroTecleado: integer; tecla1, tecla2: char; acertado: boolean; intentos: integer; lineaEscritura: integer; const MAXIMONUMERO = 99; NUMEROINTENTOS = 5; begin gd := D8BIT; gm := m640x480; (* Bajo DOS bastaria con m320x200 *) initgraph(gd, gm, ''); error := graphResult; if error <> grOk then begin writeLn('No se pudo entrar a modo grafico'); writeLn('Error encontrado: '+ graphErrorMsg(error) ); halt(1); end; intentos := 0; lineaEscritura := 50; randomize; numeroAdivinar := round(random * MAXIMONUMERO); acertado := false; setColor(14); outTextXY(10,10, 'Adivinar numeros'); repeat outTextXY(15,lineaEscritura,'Teclea dos cifras (00 a 99)'); tecla1 := readkey; outTextXY(235,lineaEscritura,tecla1); tecla2 := readkey; outTextXY(243,lineaEscritura,tecla2); numeroTecleado := (ord(tecla1)-ord('0')) * 10 +ord(tecla2)-ord('0'); if numeroTecleado = numeroAdivinar then acertado := TRUE else if numeroTecleado < numeroAdivinar then outTextXY(260, lineaEscritura, 'Corto') else if numeroTecleado > numeroAdivinar then outTextXY(260, lineaEscritura, 'Grande'); intentos := intentos + 1; lineaEscritura := lineaEscritura + 10; until acertado or (intentos = NUMEROINTENTOS); setColor(15); if acertado then outTextXY(160,180, 'Acertaste!!!') else outTextXY(160,180, 'Era: '+intToStr(numeroAdivinar)); readkey; closeGraph; end.
El modo de 320x200 puntos no está disponible en muchos casos si programamos para Windows, por lo que hemos usado un modo de 640x480 puntos, aunque la información en pantalla quede un poco más descentrada.
Si se compila para MsDos, sí se podría usar el modo 320x200. Para este sistema operativo, la línea "uses crt, wingraph;" se debería cambiar por "uses crt, graph;".
4.4. Adivinar números en Java.
Hay dos diferencias importantes con la versión en C
- La forma de acceder al teclado es algo más incómoda (por ahora, en ciertos casos nos resultará más práctica esta forma de trabajar), como ya hemos visto
- Las "cosas" dentro de programa están ordenadas de forma un poco especial: por la forma de funcionar los Applets, las rutinas de inicialización aparecen en el método "init". De igual modo, las rutinas de escritura en pantalla aparecen en el método "paint". Finalmente, las operaciones básicas que hay que realizar cuando se pulsa una tecla las he incluido en una de las rutinas de comprobación de teclado (en "keyPressed").
El resto debería ser fácil de seguir:
/*----------------------------*/ /* Intro a la programac de */ /* juegos, por Nacho Cabanes */ /* */ /* ipj04j.java */ /* */ /* Cuarto ejemplo: adivinar */ /* un numero - modo grfico */ /* */ /* Comprobado con: */ /* - JDK 1.5.0 */ /*----------------------------*/ import java.applet.Applet; import java.awt.*; import java.awt.event.*; public class ipj04j extends Applet implements KeyListener { int numeroAdivinar, numeroTecleado; char tecla1='a', tecla2='a'; boolean terminado=false; int intentos=0; int lineaEscritura=50; int i; final int MAXIMONUMERO = 99; final int NUMEROINTENTOS = 6; int respuestas[] = {-1,-1,-1,-1,-1,-1}; char letra; public void paint(Graphics g) { // Cartel "principal" g.setColor(Color.red); g.drawString("Adivinar numeros",10,10); // Escribo "n" veces lo que ya ha tecleado for (i=0; i<=intentos; i++) { g.setColor(Color.green); g.drawString("Teclea dos cifras (00 a 99)", 15,lineaEscritura+i*10); g.setColor(Color.blue); // Slo escribo la respuesta si realmente la hay if (respuestas[i] != -1){ g.drawString(""+respuestas[i], 235, lineaEscritura+i*10); // Y, de paso, compruebo si ha acertado if (respuestas[i] == numeroAdivinar) { terminado= true; g.drawString("Acertaste!!!", 160, 180); } // O si todava hay que ayudarle else if (respuestas[i] < numeroAdivinar) g.drawString("Corto", 260, lineaEscritura+i*10); else g.drawString("Grande", 260, lineaEscritura+i*10); } } // Si no quedan intentos, digo cual era if (terminado) g.drawString("Era: "+numeroAdivinar, 160, 190); } public void init() { numeroAdivinar = (int) Math.round(Math.random() * MAXIMONUMERO); addKeyListener(this); } public void keyTyped(KeyEvent e) { } public void keyReleased(KeyEvent e) { } public void keyPressed(KeyEvent e) { if (! terminado) { // Leo la primera tecla si corresponde if (tecla1 == 'a') { tecla1 = e.getKeyChar(); tecla2 = 'a'; } else // Y si no, leo la segunda { tecla2 = e.getKeyChar(); // Si era la segunda, calculo el nmero tecleado numeroTecleado = new Integer (""+tecla1+tecla2); respuestas[intentos] = numeroTecleado; tecla1='a'; intentos++; if (intentos==NUMEROINTENTOS) { terminado = true; intentos --; // Para no salirme en "respuestas[i]" } } } repaint(); } }
que tendría como resultado: