20. Descomponiendo en funciones

Nuestro "Main" ya ocupa más de 200 líneas (más de 3 páginas de papel), a pesar de tratarse de un juego muy simple. Si seguimos ampliando de esta manera, pronto llegará un momento en que sea difícil encontrar dónde se encuentra cada cosa, lo que dificultará ampliar y hacer correcciones.

Por eso, va siendo el momento de descomponer el programa en bloques más pequeños, cada uno de los cuales tenga una misión más concreta.

El primer objetivo es llegar a un Main mucho más compacto y más facil de leer, algo como:

public static void Main()
{
 
    InicializarJuego();
 
    while (! juegoTerminado )
    {        
        InicializarPartida();
        MostrarPresentacion();
 
        // ------ Bucle de juego ------
        while( ! partidaTerminada  )
        {
            Dibujar();
            ComprobarTeclas();
            MoverElementos();
            ComprobarColisiones();
            PausaFotograma();
        } // Fin del bucle de juego
    }     // Fin de partida
}

En una segunda etapa, nos permitirá simplificar tareas repetitivas, pero eso llegará un poco más adelante.

Los pasos que seguiremos para hacer el programa más modular serán los siguientes

  • Pensar en las "tareas" que realmente componen Main
  • Crear un "bloque" (una función) para cada una de esas tareas. Por ahora, para nosotros, el nombre cada una de esas tareas será típicamente una acción (como "ComprobarTeclas"), y cuando creemos el bloque, éste tendrá una apariencia muy similar a la de Main: comenzará con "public static void" y terminará con paréntesis vacíos.
  • Finalmente, las variables que se vayan a compartir entre varias funciones, deberán estar fuera de Main y antes de todas esas funciones, y deberán estar precedidas (por ahora) de la palabra "static".

Debería ser un poco trabajoso, pero no especialmente difícil. El resultado sería algo como:

// Primer mini-esqueleto de juego en modo texto
// Versión "n"
 
using System;
using System.Threading; // Para Thread.Sleep
 
public class Juego03n
{
    struct ElemGrafico {
        public float x;
        public float y;
        public int xInicial;
        public int yInicial;
        public float incrX;
        public float incrY;
        public char simbolo;
        public ConsoleColor color;
        public bool visible;
    }
 
    static ElemGrafico personaje;
 
 
    static int numPremios, numEnemigos, numObstaculos;
 
    static ElemGrafico[] obstaculos;
    static ElemGrafico[] enemigos;
    static ElemGrafico[] premios;
 
    static bool juegoTerminado;
    static int vidas;
    static int puntos;
    static bool partidaTerminada;
    static ConsoleKeyInfo tecla;  // Tecla pulsada
    static Random generador;
 
 
    public static void InicializarJuego()
    {
        juegoTerminado = false;
        numPremios = 10;
        numEnemigos = 10;
        numObstaculos = 20;
        obstaculos = new ElemGrafico[numObstaculos];
        enemigos = new ElemGrafico[numEnemigos];
        premios = new ElemGrafico[numPremios];
        generador = new Random();
    }
 
 
    public static void InicializarPartida()
    {
        // En cada partida, hay que reiniciar ciertas variables
        vidas = 3;
        puntos = 0;
        partidaTerminada = false; 
 
        personaje.xInicial = 40;
        personaje.yInicial = 12;
        personaje.x = personaje.xInicial;
        personaje.y = personaje.yInicial;
        personaje.simbolo = 'A';
        personaje.color = ConsoleColor.Yellow;
 
        // Genero las posiciones de los elementos al azar
        for (int i=0; i<numObstaculos; i++)  // Obstaculos
        {
            obstaculos[i].x = generador.Next(0,80);
            obstaculos[i].y = generador.Next(1,24);
            obstaculos[i].simbolo = 'o';
            obstaculos[i].color = ConsoleColor.Red;
        }
 
        for (int i=0; i<numEnemigos; i++)  // Enemigos
        {
            enemigos[i].x = generador.Next(0,80);
            enemigos[i].y = generador.Next(1,24);
            enemigos[i].incrX = 0.5f;
            enemigos[i].simbolo = '@';
            enemigos[i].color = ConsoleColor.Blue;
        }
 
        for (int i=0; i<numPremios; i++)  // Premios
        {
            premios[i].x = generador.Next(0,80);
            premios[i].y = generador.Next(1,24);
            premios[i].simbolo = '/';
            premios[i].color = ConsoleColor.White;
            premios[i].visible = true;
        }
    }
 
 
    public static void MostrarPresentacion()
    {
        // ---- Pantalla de presentación ----
        Console.Clear();
        Console.ForegroundColor = ConsoleColor.White;
        Console.SetCursorPosition(34,10);
        Console.Write("Jueguecillo");
        Console.SetCursorPosition(31,12);
        Console.Write("Escoja una opción:");
        Console.SetCursorPosition(15,17);
        Console.Write("1.- Jugar una partida");
        Console.SetCursorPosition(15,19);
        Console.Write("0.- Salir ");
 
        tecla = Console.ReadKey(false);
        if (tecla.KeyChar == '0')
        {
            partidaTerminada = true;
            juegoTerminado = true;
        }
    }
 
    public static void Dibujar()
    {
        // -- Dibujar --
        Console.Clear();
 
        // Marcador
        Console.Write("Vidas: {0}  -  Puntos {1}", 
            vidas, puntos);
 
        for (int i=0; i<numObstaculos; i++)  // Obstáculos
        {
            Console.SetCursorPosition(
                (int) obstaculos[i].x, (int) obstaculos[i].y);
            Console.ForegroundColor =  obstaculos[i].color;
            Console.Write( obstaculos[i].simbolo );
        }
 
        for (int i=0; i<numEnemigos; i++)  // Enemigos
        {
            Console.SetCursorPosition(
                (int) enemigos[i].x, (int) enemigos[i].y);
            Console.ForegroundColor =  enemigos[i].color;
            Console.Write( enemigos[i].simbolo );
        }
 
        for (int i=0; i<numPremios; i++)  // Premios
        {
            if (premios[i].visible)
            {
                Console.SetCursorPosition(
                    (int) premios[i].x, (int) premios[i].y);
                Console.ForegroundColor =  premios[i].color;
                Console.Write( premios[i].simbolo );
            }
        }
 
        Console.SetCursorPosition(
            (int) personaje.x, (int) personaje.y);
        Console.ForegroundColor =  personaje.color;
        Console.Write( personaje.simbolo );        
    }
 
 
    public static void ComprobarTeclas()
    {
        // -- Leer teclas y calcular nueva posición --
        if (Console.KeyAvailable)
        {
            tecla = Console.ReadKey(false);
 
            if(tecla.Key == ConsoleKey.RightArrow) personaje.x++;
            if(tecla.Key == ConsoleKey.LeftArrow) personaje.x--;
            if(tecla.Key == ConsoleKey.DownArrow) personaje.y++;
            if(tecla.Key == ConsoleKey.UpArrow) personaje.y--;
        }
 
    }
 
 
    public static void MoverElementos()
    {
        // -- Mover enemigos, entorno --
        for (int i=0; i<numEnemigos; i++)  // Enemigos
        {
            enemigos[i].x = enemigos[i].x + enemigos[i].incrX;
            if (( (int) enemigos[i].x == 0)
                    || ( (int) enemigos[i].x == 79))
                enemigos[i].incrX = - enemigos[i].incrX;
        }
    }
 
 
    public static void ComprobarColisiones()
    {
        // -- Colisiones, perder vidas, etc --
        for (int i=0; i<numObstaculos; i++)  // Obstáculos
        {
            if ((obstaculos[i].x == personaje.x)  
                && (obstaculos[i].y == personaje.y))
            {
                vidas --;
                if (vidas == 0)
                    partidaTerminada=true;
                personaje.x = personaje.xInicial;
                personaje.y = personaje.yInicial;
            }
        }
 
        for (int i=0; i<numPremios; i++)  // Obstáculos
        {
            if ((premios[i].x == personaje.x)  
                && (premios[i].y == personaje.y)
                && premios[i].visible )
            {
                puntos += 10;
                premios[i].visible = false;
            }
        }
 
        for (int i=0; i<numEnemigos; i++)  // Enemigos
        {
            if (( (int) enemigos[i].x == personaje.x)
                    && ( (int) enemigos[i].y == personaje.y))
            {
                vidas --;
                if (vidas == 0)
                    partidaTerminada=true;
                personaje.x = personaje.xInicial;
                personaje.y = personaje.yInicial;
            }
        }
 
    }
 
 
    public static void PausaFotograma()
    {
        // -- Pausa hasta el siguiente "fotograma" del juego --
        Thread.Sleep(40);
    }
 
 
    public static void Main()
    {
 
        InicializarJuego();
 
        while (! juegoTerminado )
        {        
            InicializarPartida();
            MostrarPresentacion();
 
            // ------ Bucle de juego ------
            while( ! partidaTerminada  )
            {
                Dibujar();
                ComprobarTeclas();
                MoverElementos();
                ComprobarColisiones();
                PausaFotograma();
            } // Fin del bucle de juego
        }     // Fin de partida
    }         // Fin de Main
}
 

Ejercicio propuesto: Como ésta es la última versión que haremos en modo texto, complétala para que sea realmente jugable (la mayoría de estas mejoras ya se habían propuesto en apartados anteriores):

  • Que el personaje no se pueda salir de la pantalla (porque además eso provoca que la ejecución del juego se detenga).
  • Que cuando se recojan todos los premios, éstos vuelvan a aparecer y aumente la dificultad (por ejemplo, se incremente la velocidad de los enemigos o aparezca un nuevo enemigo).
  • Que los obstáculos y los premios no puedan aparecer en la misma posición inicial que el personaje.
  • Que los enemigos no puedan aparecer en la misma coordenada Y que el personaje, para evitar que nos puedan "cazar" nada más comenzar una partida.
  • Que se memorice la puntuacón más alta obtenida en una sesión de juego.