29. Paredes que no se pueden atravesar

Hemos visto como dibujar un fondo formado a partir de "tiles" (casillas repetitivas). Ahora vamos a ver cómo comprobar colisiones con los elementos que forman ese fondo, de modo que no se puedan atravesar las paredes.

La primera idea es que tendremos que comprobar si es posible movernos a una cierta posición antes de desplazarnos a ella. Esa posición será la misma que ocupa el personaje, pero ligeramente desplazada. Por ejemplo, si nuestro personaje ocupa un rectángulo que empieza en las coordenadas (x,y) y termina en (x+ancho,y+alto), cuando se mueva hacia la derecha pasará a ocupar un rectángulo que empezará en (x+incrX,y) y termina en (x+ancho+incrX,y+alto). Del mismo modo, podemos saber qué rectángulo ocupará si se mueve a izquierda (-incrX), hacia arriba (-incrY) o hacia abajo (+incrY).

Así, podemos crear una función "EsPosibleMover", que reciba esas coordenadas mínimas y máximas del rectángulo, y compruebe si esa es una posición válida para nuestro personaje.

Esa función "EsPosibleMover" se limitaría a comprobar si chocamos con alguno de los elementos del fondo (y en ese caso no podremos mover, luego "EsPosibleMover" devolverá "false") o si no chocamos con nada (y entonces sí podremos mover, por lo que devolverá "true"):

public static bool EsPosibleMover(int x, int y, int xFin, int yFin)
{
  // Veo si choca con algún elemento del fondo
  for (int fila = 0; fila < altoFondo; fila++)  // Fondo
    for (int col = 0; col < anchoFondo; col++)
      if ((fondo[fila, col] == 1) &&  Colision(
          x,y,xFin,yFin,  // Posicion del personaje
          margenXFondo + col * anchoCasillaFondo,  // x inicial de casilla actual de fondo
          margenYFondo + fila * altoCasillaFondo,  // y inicial
          margenXFondo + (col+1) * anchoCasillaFondo,  // x final
          margenYFondo + (fila+1) * altoCasillaFondo))  // y final
        return false;
 
  // Si no ha chocado con ninguno, es posible moverse a esa posición
  return true;
}
 

Hasta ahora sólo teníamos una función "Colision" que comprobaba si chocaban dos "elementos gráficos"; como nuestro fondo no está formado por elementos gráficos, sino que nos limitamos a dibujar una imagen de forma repetitiva, necesitaríamos una variante de la función "Colisión" que sea capaz de comprobar si chocan dos rectángulos, sean elementos gráficos o no. Podría ser así:

public static bool Colision(
    int x1, int y1, int xFin1, int yFin1,
    int x2, int y2, int xFin2, int yFin2)
{
    if ((xFin2 > x1)
          && (x2 < xFin1)
          && (yFin2 > y1)
          && (y2 < yFin1))
        return true;
    else
        return false;
} 

Como se puede ver, en C# (y en otros muchos lenguajes modernos) podemos tener dos funciones que se llaman igual pero que reciben distintos parámetros. No es problema, el compilador será capaz de deducir a cual de ellas llamamos en cada caso.

Finalmente, como ya habíamos anticipado, a la hora de comprobar teclas, no cambiaremos el valor de X o Y de nuestro personaje hasta que no veamos si realmente podemos mover a una cierta posición:

if (Hardware.TeclaPulsada(Hardware.TECLA_DER)
    && EsPosibleMover(personaje.x + personaje.incrX, personaje.y, 
            personaje.x + personaje.ancho + personaje.incrX, 
            personaje.y + personaje.alto))
        personaje.x += personaje.incrX;
 
if (Hardware.TeclaPulsada(Hardware.TECLA_IZQ)
    && EsPosibleMover(personaje.x - personaje.incrX, personaje.y,
            personaje.x + personaje.ancho - personaje.incrX, 
            personaje.y + personaje.alto))
        personaje.x -= personaje.incrX;
 
...
 

Con esos cambios, el fuente completo quedaría así:

// Primer mini-esqueleto de juego en modo gráfico
// Versión "h1"
 
using System;
using System.Threading; // Para Thread.Sleep
 
public class Juego05h1
{
    public struct ElemGrafico
    {
        public int x;
        public int y;
        public int xInicial;
        public int yInicial;
        public int ancho;
        public int alto;
        public int incrX;
        public int incrY;
        public Imagen imagen;
        public bool visible;
    }
 
    static byte anchoFondo = 20;
    static byte altoFondo = 16;
    static short margenXFondo = 80;
    static byte margenYFondo = 30;
    static byte anchoCasillaFondo = 32;
    static byte altoCasillaFondo = 32;
    static Imagen imgPared;
    static public byte[,] fondo =
    {
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
    };
 
    static ElemGrafico personaje;
    static Fuente tipoDeLetra;
 
    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 Random generador;
 
    static Imagen fondoPresentacion;
    static Imagen fondoAyuda;
    static Imagen fondoCreditos;
 
 
    public static void InicializarJuego()
    {
        // Entrar a modo grafico 800x600
        bool pantallaCompleta = false;
        Hardware.Inicializar(800, 600, 24, pantallaCompleta);
 
        // Resto de inicializacion
        tipoDeLetra = new Fuente("FreeSansBold.ttf", 18);
        juegoTerminado = false;
        numPremios = 10;
        numEnemigos = 10;
        numObstaculos = 20;
        obstaculos = new ElemGrafico[numObstaculos];
        enemigos = new ElemGrafico[numEnemigos];
        premios = new ElemGrafico[numPremios];
        generador = new Random();
 
        // Cargo imágenes de elementos
        personaje.imagen = new Imagen("personaje.png");
 
        for (int i = 0; i < numObstaculos; i++)  // Obstaculos
            obstaculos[i].imagen = new Imagen("obstaculo.png");
 
        for (int i = 0; i < numEnemigos; i++)  // Enemigos
            enemigos[i].imagen = new Imagen("enemigo.png");
 
        for (int i = 0; i < numPremios; i++)  // Premios
            premios[i].imagen = new Imagen("premio.png");
 
        imgPared = new Imagen("pared.png");
 
        // Y cargo las imagenes de la presentación, ayuda y créditos
        fondoPresentacion = new Imagen("present.jpg");
        fondoAyuda = new Imagen("present.jpg");
        fondoCreditos = new Imagen("present.jpg");
    }
 
 
    public static void InicializarPartida()
    {
        // En cada partida, hay que reiniciar ciertas variables
        vidas = 3;
        puntos = 0;
        partidaTerminada = false;
 
        personaje.xInicial = 400;
        personaje.yInicial = 300;
        personaje.x = personaje.xInicial;
        personaje.y = personaje.yInicial;
        personaje.visible = true;
        personaje.ancho = 32;
        personaje.alto = 30;
        personaje.incrX = 10;
        personaje.incrY = 10;
 
        // Genero las posiciones de los elementos al azar
        for (int i = 0; i < numObstaculos; i++)  // Obstaculos
        {
            obstaculos[i].visible = true;
            obstaculos[i].ancho = 38;
            obstaculos[i].alto = 22;
            // Al colocar un obstáculo, compruebo que no choque
            // con el personaje, para que la partida
            // no acabe nada más empezar
            do
            {
                obstaculos[i].x = generador.Next(50, 700);
                obstaculos[i].y = generador.Next(30, 550);
            } while (Colision(obstaculos[i], personaje));
        }
 
        for (int i = 0; i < numEnemigos; i++)  // Enemigos
        {
            enemigos[i].incrX = 5;
            enemigos[i].visible = true;
            enemigos[i].ancho = 36;
            enemigos[i].alto = 42;
            enemigos[i].x = generador.Next(50, 700);
            // Para la Y, compruebo que no sea del rango de
            // la del personaje, para que la partida
            // no acabe nada más empezar
            do
            {
                enemigos[i].y = generador.Next(30, 550);
            } while ((enemigos[i].y + enemigos[i].alto > personaje.y)
                && (enemigos[i].y < personaje.y + personaje.alto));
        }
 
        for (int i = 0; i < numPremios; i++)  // Premios
        {
            premios[i].x = generador.Next(50, 700);
            premios[i].y = generador.Next(30, 550);
            premios[i].visible = true;
            premios[i].ancho = 34;
            premios[i].alto = 18;
        }
    }
 
 
    public static void MostrarPresentacion()
    {
        bool finPresentacion = false;
 
        do
        {
            // ---- Pantalla de presentación --
            Hardware.BorrarPantallaOculta(0, 0, 0);
 
            // Fondo de la presentación
            fondoPresentacion.DibujarOculta(0, 0);
 
            // Marcador
            Hardware.EscribirTextoOculta("Jueguecillo",
                     340, 200, // Coordenadas
                     255, 255, 255, // Colores
                     tipoDeLetra);
 
            Hardware.EscribirTextoOculta("Escoja una opción:",
                     310, 300, // Coordenadas
                     200, 200, 200, // Colores
                     tipoDeLetra);
 
            Hardware.EscribirTextoOculta("J.- Jugar una partida",
                     150, 390, // Coordenadas
                     200, 200, 200, // Colores
                    tipoDeLetra);
 
            Hardware.EscribirTextoOculta("A.- Ayuda",
                     150, 430, // Coordenadas
                     200, 200, 200, // Colores
                     tipoDeLetra);
 
            Hardware.EscribirTextoOculta("C.- Créditos",
                     150, 470, // Coordenadas
                     200, 200, 200, // Colores
                     tipoDeLetra);
 
            Hardware.EscribirTextoOculta("S.- Salir",
                     150, 510, // Coordenadas
                     200, 200, 200, // Colores
                     tipoDeLetra);
 
            Hardware.VisualizarOculta();
 
            Hardware.Pausa(20);
 
            if (Hardware.TeclaPulsada(Hardware.TECLA_A))
                MostrarAyuda();
 
            if (Hardware.TeclaPulsada(Hardware.TECLA_C))
                MostrarCreditos();
 
            if (Hardware.TeclaPulsada(Hardware.TECLA_J))
                finPresentacion = true;
 
            if (Hardware.TeclaPulsada(Hardware.TECLA_S))
            {
                finPresentacion = true;
                partidaTerminada = true;
                juegoTerminado = true;
            }
        } while (!finPresentacion);
    }
 
 
    public static void MostrarAyuda()
    {
        string[] textosAyuda =
        {
          "Recoge los premios",
          "Evita los obstáculos y los enemigos",
          "Usa las flechas de cursor para mover"
        };
 
        // ---- Pantalla de presentación --
        Hardware.BorrarPantallaOculta(0, 0, 0);
 
        // Fondo de la presentación
        fondoAyuda.DibujarOculta(0, 0);
 
        // Marcador
        Hardware.EscribirTextoOculta("Ayuda",
                 340, 200, // Coordenadas
                 255, 255, 255, // Colores
                tipoDeLetra);
 
        // Textos repetitivos
        short posicYtexto = 280;
        foreach (string texto in textosAyuda)
        {
            Hardware.EscribirTextoOculta(texto,
                     150, posicYtexto, // Coordenadas
                     200, 200, 200, // Colores
                    tipoDeLetra);
            posicYtexto += 30;
        }
 
        Hardware.EscribirTextoOculta("ESC- volver",
                 650, 530, // Coordenadas
                 200, 200, 200, // Colores
                tipoDeLetra);
        Hardware.VisualizarOculta();
 
        do
        {
            Hardware.Pausa(20);
        } while (!Hardware.TeclaPulsada(Hardware.TECLA_ESC));
    }
 
 
    public static void MostrarCreditos()
    {
        // ---- Pantalla de presentación --
        Hardware.BorrarPantallaOculta(0, 0, 0);
 
        // Fondo de la presentación
        fondoCreditos.DibujarOculta(0, 0);
 
        // Marcador
        Hardware.EscribirTextoOculta("Creditos",
                 250, 200, // Coordenadas
                 255, 255, 255, // Colores
                tipoDeLetra);
 
        Hardware.EscribirTextoOculta("Por Nacho Cabanes, 2011",
                  250, 300, // Coordenadas
                  200, 200, 200, // Colores
                 tipoDeLetra);
 
        Hardware.EscribirTextoOculta("ESC- volver",
                 650, 530, // Coordenadas
                 200, 200, 200, // Colores
                tipoDeLetra);
        Hardware.VisualizarOculta();
 
        do
        {
            Hardware.Pausa(20);
        } while (!Hardware.TeclaPulsada(Hardware.TECLA_ESC));
    }
 
 
    public static void Dibujar()
    {
        // -- Dibujar --
        Hardware.BorrarPantallaOculta(0, 0, 0);
 
        // Marcador
        Hardware.EscribirTextoOculta("Vidas        Puntos",
                 0, 0, // Coordenadas
                 255, 255, 255, // Colores
                tipoDeLetra);
 
        Hardware.EscribirTextoOculta(Convert.ToString(vidas),
                 70, 0, // Coordenadas
                 200, 200, 200, // Colores
                tipoDeLetra);
 
        Hardware.EscribirTextoOculta(Convert.ToString(puntos),
                 190, 0, // Coordenadas
                 200, 200, 200, // Colores
                tipoDeLetra);
 
        // Pared de fondo
        for (int fila = 0; fila < altoFondo; fila++)  // Fondo
            for (int col = 0; col < anchoFondo; col++)
                if (fondo[fila, col] == 1)
                    imgPared.DibujarOculta(
                        margenXFondo + col * anchoCasillaFondo,
                        margenYFondo + fila * altoCasillaFondo);
 
        for (int i = 0; i < numObstaculos; i++)  // Obstáculos
        {
            obstaculos[i].imagen.DibujarOculta(
                (int)obstaculos[i].x, (int)obstaculos[i].y);
        }
 
        for (int i = 0; i < numEnemigos; i++)  // Enemigos
        {
            enemigos[i].imagen.DibujarOculta(
                (int)enemigos[i].x, (int)enemigos[i].y);
        }
 
        for (int i = 0; i < numPremios; i++)  // Premios
        {
            if (premios[i].visible)
            {
                premios[i].imagen.DibujarOculta(
                    premios[i].x, premios[i].y);
            }
        }
 
        personaje.imagen.DibujarOculta(
            personaje.x, personaje.y);
 
        // Finalmente, muestro en pantalla
        Hardware.VisualizarOculta();
    }
 
 
    public static void ComprobarTeclas()
    {
        // -- Leer teclas y calcular nueva posición --
        if (Hardware.TeclaPulsada(Hardware.TECLA_ESC))
            partidaTerminada = true;
 
        if (Hardware.TeclaPulsada(Hardware.TECLA_DER)
            && EsPosibleMover(personaje.x + personaje.incrX, personaje.y, 
                    personaje.x + personaje.ancho + personaje.incrX, personaje.y + personaje.alto))
                personaje.x += personaje.incrX;
        if (Hardware.TeclaPulsada(Hardware.TECLA_IZQ)
            && EsPosibleMover(personaje.x - personaje.incrX, personaje.y,
                    personaje.x + personaje.ancho - personaje.incrX, personaje.y + personaje.alto))
                personaje.x -= personaje.incrX;
        if (Hardware.TeclaPulsada(Hardware.TECLA_ARR)
            && EsPosibleMover(personaje.x, personaje.y - personaje.incrY,
                    personaje.x + personaje.ancho, personaje.y + personaje.alto - personaje.incrY))
                personaje.y -= personaje.incrY;
        if (Hardware.TeclaPulsada(Hardware.TECLA_ABA)
            && EsPosibleMover(personaje.x, personaje.y + personaje.incrY,
                    personaje.x + personaje.ancho, personaje.y + personaje.alto + personaje.incrY))
                personaje.y += personaje.incrY;
    }
 
 
    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 <= 50)
                    || ((int)enemigos[i].x >= 700))
                enemigos[i].incrX = -enemigos[i].incrX;
        }
    }
 
 
    public static void ComprobarColisiones()
    {
        // -- Colisiones, perder vidas, etc --
        for (int i = 0; i < numObstaculos; i++)  // Obstáculos
        {
            if (Colision(obstaculos[i], personaje))
            {
                vidas--;
                if (vidas == 0)
                    partidaTerminada = true;
                personaje.x = personaje.xInicial;
                personaje.y = personaje.yInicial;
            }
        }
 
        for (int i = 0; i < numPremios; i++)  // Premios
        {
            if (Colision(premios[i], personaje))
            {
                puntos += 10;
                premios[i].visible = false;
            }
        }
 
        for (int i = 0; i < numEnemigos; i++)  // Enemigos
        {
            if (Colision(enemigos[i], personaje))
            {
                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 --
        Hardware.Pausa(20);
    }
 
 
    public static bool Colision(ElemGrafico e1, ElemGrafico e2)
    {
        // No se debe chocar con un elemento oculto
        if ((e1.visible == false) || (e2.visible == false))
            return false;
        // Ahora ya compruebo coordenadas
        if ((e1.x + e1.ancho > e2.x)
              && (e1.x < e2.x + e2.ancho)
              && (e1.y + e1.alto > e2.y)
              && (e1.y < e2.y + e2.alto))
            return true;
        else
            return false;
    }
 
 
    public static bool Colision(
        int x1, int y1, int xFin1, int yFin1,
        int x2, int y2, int xFin2, int yFin2)
    {
        if ((xFin2 > x1)
              && (x2 < xFin1)
              && (yFin2 > y1)
              && (y2 < yFin1))
            return true;
        else
            return false;
    } 
 
 
    public static bool EsPosibleMover(int x, int y, int xFin, int yFin)
    {
        // Veo si choca con algún elemento del fondo
        for (int fila = 0; fila < altoFondo; fila++)  // Fondo
            for (int col = 0; col < anchoFondo; col++)
                if ((fondo[fila, col] == 1) &&  Colision(
                        x,y,xFin,yFin,  // Posicion del personaje
                        margenXFondo + col * anchoCasillaFondo,  // x inicial de casilla actual de fondo
                        margenYFondo + fila * altoCasillaFondo,  // y inicial
                        margenXFondo + (col+1) * anchoCasillaFondo,  // x final
                        margenYFondo + (fila+1) * altoCasillaFondo))  // y final
                    return false;
 
        // Si no ha chocado con ninguno, es posible moverse a esa posición
        return true;
    }
 
 
 
 
    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
}
 



Este planteamiento permite gastar muy poca memoria, porque la imagen repetitiva del fondo se carga sólo una vez. A cambio, complica otras posibilidades, como la de incluir los premios y obstáculos mortales como parte del "mapa" del fondo. Por eso, también podemos usar un planteamiento alternativo: a partir del "mapa" del fondo, crear un array de elementos gráficos, y comprobar colisiones directamente con todos esos elementos gráficos.

Si lo planteamos así, la función "ComprobarTeclas" no cambiará, pero sí lo hará "EsPosibleMover", que comprobará colisiones y también ampliaremos "InicializarJuego" para preparar ese nuevo array de elementos gráficos.

Vamos a empezar por ver los cambios en InicializarJuego: crearíamos un nuevo array, que ni siquiera hace falta que sea tan grande como el fondo, sino que puede estar formado por las casillas que no estén vacías:

// Cuento la cantidad de elementos reales en el fondo        
elementosFondo = 0;
for (int fila = 0; fila < altoFondo; fila++)  // Fondo
    for (int col = 0; col < anchoFondo; col++)
        if (fondo[fila, col] != 0)
            elementosFondo++;
// Y reservo espacio para el array que los contiene
fondos = new ElemGrafico[elementosFondo];
// y para cada uno de uno de ellos
int posicFondo = 0;
for (int fila = 0; fila < altoFondo; fila++)  // Fondo
    for (int col = 0; col < anchoFondo; col++)
        if (fondo[fila, col] != 0)
        {
            fondos[posicFondo].x =  margenXFondo + col * anchoCasillaFondo;
            fondos[posicFondo].y =  margenYFondo + fila * altoCasillaFondo;
            fondos[posicFondo].imagen = new Imagen("pared.png");
            fondos[posicFondo].ancho = anchoCasillaFondo;
            fondos[posicFondo].alto = altoCasillaFondo;
            fondos[posicFondo].visible =  true;
            posicFondo++;
        }
 

En vez de crear una nueva función "Colision" alternativa, podemos hacer que "EsPosibleMover" sea la que compruebe si los rectángulos de esos nuevos elementos gráficos se solapan con las coordenadas que le indiquemos:

public static bool EsPosibleMover(int x, int y, int xFin, int yFin)
{
    // Veo si choca con algún elemento del fondo
    for (int i = 0; i < elementosFondo; i++)
        if ((fondos[i].x + fondos[i].ancho > x)
          && (fondos[i].x < xFin)
        && (fondos[i].y + fondos[i].alto > y)
          && (fondos[i].y < yFin))
        return false;
 
    // Si no ha chocado con ninguno, es posible moverse a esa posición
    return true;
}

Y ya que estamos, como tenemos un array de elementos gráficos, podemos simplificar la parte de "Dibujar" que se encarga de mostrar el fondo:

for (int i = 0; i < elementosFondo; i++)  // Imágenes del fondo
  fondos[i].imagen.DibujarOculta(fondos[i].x, fondos[i].y);
 

Con estos cambios, esta versión alternativa del fuente sería:

// Primer mini-esqueleto de juego en modo gráfico
// Versión "h"
 
using System;
using System.Threading; // Para Thread.Sleep
 
public class Juego05h
{
    public struct ElemGrafico
    {
        public int x;
        public int y;
        public int xInicial;
        public int yInicial;
        public int ancho;
        public int alto;
        public int incrX;
        public int incrY;
        public Imagen imagen;
        public bool visible;
    }
 
    static byte anchoFondo = 20;
    static byte altoFondo = 16;
    static short margenXFondo = 80;
    static byte margenYFondo = 30;
    static byte anchoCasillaFondo = 32;
    static byte altoCasillaFondo = 32;
    static Imagen imgPared;
    static public byte[,] fondo =
    {
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
    };
 
    static ElemGrafico personaje;
    static Fuente tipoDeLetra;
 
    static int numPremios, numEnemigos, numObstaculos;
 
    static ElemGrafico[] obstaculos;
    static ElemGrafico[] enemigos;
    static ElemGrafico[] premios;
    static ElemGrafico[] fondos;
    static int elementosFondo;
 
    static bool juegoTerminado;
    static int vidas;
    static int puntos;
    static bool partidaTerminada;
    static Random generador;
 
    static Imagen fondoPresentacion;
    static Imagen fondoAyuda;
    static Imagen fondoCreditos;
 
 
    public static void InicializarJuego()
    {
        // Entrar a modo grafico 800x600
        bool pantallaCompleta = false;
        Hardware.Inicializar(800, 600, 24, pantallaCompleta);
 
        // Resto de inicializacion
        tipoDeLetra = new Fuente("FreeSansBold.ttf", 18);
        juegoTerminado = false;
        numPremios = 10;
        numEnemigos = 10;
        numObstaculos = 20;
        obstaculos = new ElemGrafico[numObstaculos];
        enemigos = new ElemGrafico[numEnemigos];
        premios = new ElemGrafico[numPremios];
        generador = new Random();
 
        // Cuento la cantidad de elementos reales en el fondo        
        elementosFondo = 0;
        for (int fila = 0; fila < altoFondo; fila++)  // Fondo
            for (int col = 0; col < anchoFondo; col++)
                if (fondo[fila, col] != 0)
                    elementosFondo++;
        // Y reservo espacio para el array que los contiene
        fondos = new ElemGrafico[elementosFondo];
        // y para cada uno de uno de ellos
        int posicFondo = 0;
        for (int fila = 0; fila < altoFondo; fila++)  // Fondo
            for (int col = 0; col < anchoFondo; col++)
                if (fondo[fila, col] != 0)
                {
                    fondos[posicFondo].x =  margenXFondo + col * anchoCasillaFondo;
                    fondos[posicFondo].y =  margenYFondo + fila * altoCasillaFondo;
                    fondos[posicFondo].imagen = new Imagen("pared.png");
                    fondos[posicFondo].ancho = anchoCasillaFondo;
                    fondos[posicFondo].alto = altoCasillaFondo;
                    fondos[posicFondo].visible =  true;
                    posicFondo++;
                }
 
        // Cargo imágenes de elementos
        personaje.imagen = new Imagen("personaje.png");
 
        for (int i = 0; i < numObstaculos; i++)  // Obstaculos
            obstaculos[i].imagen = new Imagen("obstaculo.png");
 
        for (int i = 0; i < numEnemigos; i++)  // Enemigos
            enemigos[i].imagen = new Imagen("enemigo.png");
 
        for (int i = 0; i < numPremios; i++)  // Premios
            premios[i].imagen = new Imagen("premio.png");
 
        imgPared = new Imagen("pared.png");
 
        // Y cargo las imagenes de la presentación, ayuda y créditos
        fondoPresentacion = new Imagen("present.jpg");
        fondoAyuda = new Imagen("present.jpg");
        fondoCreditos = new Imagen("present.jpg");
    }
 
 
    public static void InicializarPartida()
    {
        // En cada partida, hay que reiniciar ciertas variables
        vidas = 3;
        puntos = 0;
        partidaTerminada = false;
 
        personaje.xInicial = 400;
        personaje.yInicial = 300;
        personaje.x = personaje.xInicial;
        personaje.y = personaje.yInicial;
        personaje.visible = true;
        personaje.ancho = 32;
        personaje.alto = 30;
        personaje.incrX = 10;
        personaje.incrY = 10;
 
        // Genero las posiciones de los elementos al azar
        for (int i = 0; i < numObstaculos; i++)  // Obstaculos
        {
            obstaculos[i].visible = true;
            obstaculos[i].ancho = 38;
            obstaculos[i].alto = 22;
            // Al colocar un obstáculo, compruebo que no choque
            // con el personaje, para que la partida
            // no acabe nada más empezar
            do
            {
                obstaculos[i].x = generador.Next(50, 700);
                obstaculos[i].y = generador.Next(30, 550);
            } while (Colision(obstaculos[i], personaje));
        }
 
        for (int i = 0; i < numEnemigos; i++)  // Enemigos
        {
            enemigos[i].incrX = 5;
            enemigos[i].visible = true;
            enemigos[i].ancho = 36;
            enemigos[i].alto = 42;
            enemigos[i].x = generador.Next(50, 700);
            // Para la Y, compruebo que no sea del rango de
            // la del personaje, para que la partida
            // no acabe nada más empezar
            do
            {
                enemigos[i].y = generador.Next(30, 550);
            } while ((enemigos[i].y + enemigos[i].alto > personaje.y)
                && (enemigos[i].y < personaje.y + personaje.alto));
        }
 
        for (int i = 0; i < numPremios; i++)  // Premios
        {
            premios[i].x = generador.Next(50, 700);
            premios[i].y = generador.Next(30, 550);
            premios[i].visible = true;
            premios[i].ancho = 34;
            premios[i].alto = 18;
        }
    }
 
 
    public static void MostrarPresentacion()
    {
        bool finPresentacion = false;
 
        do
        {
            // ---- Pantalla de presentación --
            Hardware.BorrarPantallaOculta(0, 0, 0);
 
            // Fondo de la presentación
            fondoPresentacion.DibujarOculta(0, 0);
 
            // Marcador
            Hardware.EscribirTextoOculta("Jueguecillo",
                     340, 200, // Coordenadas
                     255, 255, 255, // Colores
                     tipoDeLetra);
 
            Hardware.EscribirTextoOculta("Escoja una opción:",
                     310, 300, // Coordenadas
                     200, 200, 200, // Colores
                     tipoDeLetra);
 
            Hardware.EscribirTextoOculta("J.- Jugar una partida",
                     150, 390, // Coordenadas
                     200, 200, 200, // Colores
                    tipoDeLetra);
 
            Hardware.EscribirTextoOculta("A.- Ayuda",
                     150, 430, // Coordenadas
                     200, 200, 200, // Colores
                     tipoDeLetra);
 
            Hardware.EscribirTextoOculta("C.- Créditos",
                     150, 470, // Coordenadas
                     200, 200, 200, // Colores
                     tipoDeLetra);
 
            Hardware.EscribirTextoOculta("S.- Salir",
                     150, 510, // Coordenadas
                     200, 200, 200, // Colores
                     tipoDeLetra);
 
            Hardware.VisualizarOculta();
 
            Hardware.Pausa(20);
 
            if (Hardware.TeclaPulsada(Hardware.TECLA_A))
                MostrarAyuda();
 
            if (Hardware.TeclaPulsada(Hardware.TECLA_C))
                MostrarCreditos();
 
            if (Hardware.TeclaPulsada(Hardware.TECLA_J))
                finPresentacion = true;
 
            if (Hardware.TeclaPulsada(Hardware.TECLA_S))
            {
                finPresentacion = true;
                partidaTerminada = true;
                juegoTerminado = true;
            }
        } while (!finPresentacion);
    }
 
 
    public static void MostrarAyuda()
    {
        string[] textosAyuda =
        {
          "Recoge los premios",
          "Evita los obstáculos y los enemigos",
          "Usa las flechas de cursor para mover"
        };
 
        // ---- Pantalla de presentación --
        Hardware.BorrarPantallaOculta(0, 0, 0);
 
        // Fondo de la presentación
        fondoAyuda.DibujarOculta(0, 0);
 
        // Marcador
        Hardware.EscribirTextoOculta("Ayuda",
                 340, 200, // Coordenadas
                 255, 255, 255, // Colores
                tipoDeLetra);
 
        // Textos repetitivos
        short posicYtexto = 280;
        foreach (string texto in textosAyuda)
        {
            Hardware.EscribirTextoOculta(texto,
                     150, posicYtexto, // Coordenadas
                     200, 200, 200, // Colores
                    tipoDeLetra);
            posicYtexto += 30;
        }
 
        Hardware.EscribirTextoOculta("ESC- volver",
                 650, 530, // Coordenadas
                 200, 200, 200, // Colores
                tipoDeLetra);
        Hardware.VisualizarOculta();
 
        do
        {
            Hardware.Pausa(20);
        } while (!Hardware.TeclaPulsada(Hardware.TECLA_ESC));
    }
 
 
    public static void MostrarCreditos()
    {
        // ---- Pantalla de presentación --
        Hardware.BorrarPantallaOculta(0, 0, 0);
 
        // Fondo de la presentación
        fondoCreditos.DibujarOculta(0, 0);
 
        // Marcador
        Hardware.EscribirTextoOculta("Creditos",
                 250, 200, // Coordenadas
                 255, 255, 255, // Colores
                tipoDeLetra);
 
        Hardware.EscribirTextoOculta("Por Nacho Cabanes, 2011",
                  250, 300, // Coordenadas
                  200, 200, 200, // Colores
                 tipoDeLetra);
 
        Hardware.EscribirTextoOculta("ESC- volver",
                 650, 530, // Coordenadas
                 200, 200, 200, // Colores
                tipoDeLetra);
        Hardware.VisualizarOculta();
 
        do
        {
            Hardware.Pausa(20);
        } while (!Hardware.TeclaPulsada(Hardware.TECLA_ESC));
    }
 
 
    public static void Dibujar()
    {
        // -- Dibujar --
        Hardware.BorrarPantallaOculta(0, 0, 0);
 
        // Marcador
        Hardware.EscribirTextoOculta("Vidas        Puntos",
                 0, 0, // Coordenadas
                 255, 255, 255, // Colores
                tipoDeLetra);
 
        Hardware.EscribirTextoOculta(Convert.ToString(vidas),
                 70, 0, // Coordenadas
                 200, 200, 200, // Colores
                tipoDeLetra);
 
        Hardware.EscribirTextoOculta(Convert.ToString(puntos),
                 190, 0, // Coordenadas
                 200, 200, 200, // Colores
                tipoDeLetra);
 
        for (int i = 0; i < elementosFondo; i++)  // Imágenes del fondo
            fondos[i].imagen.DibujarOculta(fondos[i].x, fondos[i].y);
 
        for (int i = 0; i < numObstaculos; i++)  // Obstáculos
            obstaculos[i].imagen.DibujarOculta(obstaculos[i].x, obstaculos[i].y);
 
        for (int i = 0; i < numEnemigos; i++)  // Enemigos
            enemigos[i].imagen.DibujarOculta(enemigos[i].x, enemigos[i].y);
 
        for (int i = 0; i < numPremios; i++)  // Premios
            if (premios[i].visible)
                premios[i].imagen.DibujarOculta(premios[i].x, premios[i].y);
 
        personaje.imagen.DibujarOculta(
            personaje.x, personaje.y);
 
        // Finalmente, muestro en pantalla
        Hardware.VisualizarOculta();
    }
 
 
    public static void ComprobarTeclas()
    {
        // -- Leer teclas y calcular nueva posición --
        if (Hardware.TeclaPulsada(Hardware.TECLA_ESC))
            partidaTerminada = true;
 
        if (Hardware.TeclaPulsada(Hardware.TECLA_DER)
            && EsPosibleMover(personaje.x + personaje.incrX, personaje.y, 
                    personaje.x + personaje.ancho + personaje.incrX, personaje.y + personaje.alto))
                personaje.x += personaje.incrX;
        if (Hardware.TeclaPulsada(Hardware.TECLA_IZQ)
            && EsPosibleMover(personaje.x - personaje.incrX, personaje.y,
                    personaje.x + personaje.ancho - personaje.incrX, personaje.y + personaje.alto))
                personaje.x -= personaje.incrX;
        if (Hardware.TeclaPulsada(Hardware.TECLA_ARR)
            && EsPosibleMover(personaje.x, personaje.y - personaje.incrY,
                    personaje.x + personaje.ancho, personaje.y + personaje.alto - personaje.incrY))
                personaje.y -= personaje.incrY;
        if (Hardware.TeclaPulsada(Hardware.TECLA_ABA)
            && EsPosibleMover(personaje.x, personaje.y + personaje.incrY,
                    personaje.x + personaje.ancho, personaje.y + personaje.alto + personaje.incrY))
                personaje.y += personaje.incrY;
    }
 
 
    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 <= 50)
                    || ((int)enemigos[i].x >= 700))
                enemigos[i].incrX = -enemigos[i].incrX;
        }
    }
 
 
    public static void ComprobarColisiones()
    {
        // -- Colisiones, perder vidas, etc --
        for (int i = 0; i < numObstaculos; i++)  // Obstáculos
        {
            if (Colision(obstaculos[i], personaje))
            {
                vidas--;
                if (vidas == 0)
                    partidaTerminada = true;
                personaje.x = personaje.xInicial;
                personaje.y = personaje.yInicial;
            }
        }
 
        for (int i = 0; i < numPremios; i++)  // Premios
        {
            if (Colision(premios[i], personaje))
            {
                puntos += 10;
                premios[i].visible = false;
            }
        }
 
        for (int i = 0; i < numEnemigos; i++)  // Enemigos
        {
            if (Colision(enemigos[i], personaje))
            {
                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 --
        Hardware.Pausa(20);
    }
 
 
    public static bool Colision(ElemGrafico e1, ElemGrafico e2)
    {
        // No se debe chocar con un elemento oculto
        if ((e1.visible == false) || (e2.visible == false))
            return false;
        // Ahora ya compruebo coordenadas
        if ((e1.x + e1.ancho > e2.x)
              && (e1.x < e2.x + e2.ancho)
              && (e1.y + e1.alto > e2.y)
              && (e1.y < e2.y + e2.alto))
            return true;
        else
            return false;
    }
 
 
    public static bool EsPosibleMover(int x, int y, int xFin, int yFin)
    {
        // Veo si choca con algún elemento del fondo
        for (int i = 0; i < elementosFondo; i++)
            if ((fondos[i].x + fondos[i].ancho > x)
              && (fondos[i].x < xFin)
            && (fondos[i].y + fondos[i].alto > y)
              && (fondos[i].y < yFin))
            return false;
 
        // Si no ha chocado con ninguno, es posible moverse a esa posición
        return true;
    }
 
 
    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 (1): Mejora esta versión del juego, para que el fondo no esté formado por un único tipo de casilla, sino por dos o más.

Ejercicio propuesto (2): Haz que los premios que se pueden recoger no aparezcan en posiciones al azar, sino que sean parte también del mapa del nivel, además de los ladrillos del fondo. (Si no te sale, no te preocupes, pronto veremos cómo hacerlo)