10. El enemigo se mueve "solo"

Hasta ahora, nuestro juego "se queda parado" hasta que pulsemos una tecla. Eso es aceptable cuando nuestro personaje es lo único que se mueve, pero en la mayoría de casos no es aceptable, y ya hemos visto que hace un efecto "desconcertante" ahora que tenemos un enemigo... que sólo se mueve exactamente en el mismo momento que nosotros.

La solución es sencilla: no dejar el juego parado, sino comprobar si hay una tecla disponible, y sólo en ese caso mirar de qué tecla se trata y mover nuestro personaje (si corresponde). Eso lo podemos hacer con la construcción "if (Console.KeyAvailable) ... "

Eso todavía no es perfecto, porque permitirá que nuestro enemigo se mueva aunque nosotros no lo hagamos, pero se moverá a la velocidad máxima que permita nuestro ordenador, lo que generalmente no es jugable.

Nos interesa que se mueva solo, pero no tan rápido, sino a una velocidad estable, que sea la misma en cualquier ordenador. Lo podemos conseguir añadiendo una pequeña pausa al final de cada fotograma. Por ejemplo, si queremos que nuestro juego se mueva a 25 fotogramas por segundo (25 fps), podríamos hacer una pausa de 1000 / 25 = 40 milisegundos. Para hacer esta pausa podemos usar "Thread.Sleep(40)", que nos obliga a incluir "using System.Threading;" al principio de nuestro programa.

En un juego real, si queremos que se tarde 40 milisegundos en cada fotograma (para que la velocidad realmente sea de 25 fps), no deberíamos esperar 40 milisegundos, sino contar cuánto hemos tardado en dibujar, calcular siguientes posiciones, comprobar colisiones, etc.; si estas tareas nos llevan 15 ms, deberíamos esperar otros 25 ms, para hacer un total de 40 milisegundos por fotograma. En nuestro juego, estas tareas son tan sencillas (por ahora) que tardan un tiempo despreciable, así que todavía no nos molestaremos en contar tiempos.

Con estas consideraciones, el juego podría quedar así:

// Primer mini-esqueleto de juego en modo texto
// Versión "e"
 
using System;
using System.Threading; // Para Thread.Sleep
 
public class Juego03e
{
    public static void Main()
    {
        ConsoleKeyInfo tecla;
        int x = 40, y=12;
        int xo1 = 20, yo1 = 15;  // Obstáculo 1
        int xo2 = 25, yo2 = 5;   // Obstáculo 2
        int xo3 = 62, yo3 = 21;  // Obstáculo 3
        int xe1 = 10, ye1 = 10, incr1 = 1;  // Enemigo 1
        int fin = 0;   // 0 = no terminado, 1 = terminado
 
        // Bucle de juego
        while( fin == 0 )
        {
            // Dibujar
            Console.Clear();
            Console.SetCursorPosition(x, y);
            Console.Write("A");
 
            Console.SetCursorPosition(xo1, yo1); // Obstáculos
            Console.Write("o");
 
            Console.SetCursorPosition(xo2, yo2);
            Console.Write("o");
 
            Console.SetCursorPosition(xo3, yo3);
            Console.Write("o");
 
            Console.SetCursorPosition(xe1, ye1); // Enemigo
            Console.Write("@");
 
            // Leer teclas y calcular nueva posición
            if (Console.KeyAvailable)
            {
                tecla = Console.ReadKey(false);
 
                if(tecla.Key == ConsoleKey.RightArrow) x++;
                if(tecla.Key == ConsoleKey.LeftArrow) x--;
                if(tecla.Key == ConsoleKey.DownArrow) y++;
                if(tecla.Key == ConsoleKey.UpArrow) y--;
            }
 
            // Mover enemigos, entorno
            xe1 = xe1 + incr1;
 
            if ((xe1 == 0) || (xe1 == 79))
                incr1 = - incr1;
 
            // Colisiones, perder vidas, etc
            if ( ( (x==xo1) && (y==yo1) )
                || ( (x==xo2) && (y==yo2) )
                || ( (x==xo3) && (y==yo3) )
               )
               fin = 1; 
 
            // Pausa hasta el siguiente "fotograma" del juego
            Thread.Sleep(40);
        }
    }
}
 

Ejercicio propuesto: Amplía el juego para que realmente calcule el tiempo empleado en cada fotograma (mirando los milisegundos de la hora actual), y espere menos de 40 ms si es necesario.