Saltar a contenido

Ejercicios de C# · Refuerzo 1

La Odiseus sigue una ruta de puntos de paso en la pantalla de navegación

Instrucciones generales

Las tandas de refuerzo son opcionales y sirven para seguir practicando por tu cuenta. A diferencia de las cuatro partes anteriores, aquí cada ejercicio mezcla varios de los contenidos de la introducción a C#. Hay tres tandas de 15 ejercicios:

  • Refuerzo 1 (esta página): variables, control de flujo, arrays y métodos.
  • Refuerzo 2: colecciones, métodos y primeras clases.
  • Refuerzo 3: clases, herencia y composición.

Estos ejercicios no se entregan ni se corrigen. Por eso, al final de cada uno encontrarás, además de la pista, una posible solución oculta:

  • Intenta resolver el ejercicio antes de abrirla. Si te atascas, mira primero la pista.
  • Tu programa no tiene por qué ser igual que la solución: hay muchas formas correctas de resolver cada ejercicio. Lo importante es que la consola muestre la salida esperada y que la nave haga lo que se indica.
  • Cuando lo tengas, compara tu código con la solución y pregúntate en qué se diferencian y por qué.

Procede del mismo modo que en las partes anteriores y reutiliza el mismo proyecto. Crea un script para cada ejercicio (EjercicioR1, EjercicioR2…) y asígnalo a la nave. Recuerda dejar activo solo el script del ejercicio con el que estés trabajando.

Antes de cada ejercicio

Varios ejercicios mueven, giran o cambian de color la nave. Para que el resultado sea el que se indica, la nave debe empezar en la posición (0, 0, 0), sin rotación y con escala 1: compruébalo en el componente Transform del inspector. Los cambios que hace un script mientras el juego está en play se deshacen al pararlo.

La tripulación de la Odiseus

Estos ejercicios continúan el viaje de la Odiseus, la nave de la serie de animación Ulises 31. Tú eres Ulises, comandante de la Odiseus y, además, su piloto. A bordo te acompañan:

Tripulante Puesto Lo que conviene saber
Shirka Ordenador de a bordo Lo sabe todo y no se cansa de recordártelo. Suele irse de mantenimiento en el peor momento.
Ulises (tú) Comandante y piloto Siempre tienes calor, siempre tienes una opinión y nunca sueltas la taza de café.
Telémaco Grumete, 12 años Tu hijo. Quiere pilotar la nave cuanto antes; de momento, pilota el simulador.
Nono Ingeniero El robot que le regalaste a Telémaco. Arregla cualquier cosa… si le dejas tiempo. Siempre tiene frío: dice que se le oxidan los circuitos.
Thais Artillera Viene del planeta Zotra y tiene poderes telepáticos, pero eso no le impide llegar tarde a todo, incluido el despegue.

Órdenes para la nave

En estos ejercicios la nave no se limita a escribir en la consola. Estas son las órdenes que vas a necesitar; cada ejercicio te recuerda la que le hace falta:

Para… Orden
Mover la nave (aquí, 1 a la derecha y 2 hacia arriba) transform.Translate(new Vector2(1, 2));
Girar la nave 90° en sentido antihorario transform.Rotate(new Vector3(0, 0, 90));
Colocar la nave en un punto concreto transform.position = new Vector2(3, -1);
Cambiar el tamaño de la nave (aquí, al doble) transform.localScale = new Vector3(2, 2, 1);
Ocultar la nave (true para volver a mostrarla) GetComponent<SpriteRenderer>().enabled = false;
Teñir la nave de un color GetComponent<SpriteRenderer>().color = Color.red;

GetComponent<SpriteRenderer>() devuelve el componente Sprite Renderer de la nave, que es el que la dibuja en pantalla. Algunos de los colores disponibles son Color.red, Color.green, Color.blue, Color.yellow, Color.cyan, Color.magenta y Color.white (este último deja la nave con sus colores originales).

Ejercicio R-1. Inventario de la bodega

arrays for if constantes métodos Dificultad ★☆☆

Diario de a bordo

Antes de alejarte de la última estación espacial, toca revisar la bodega. Shirka insiste en que quedarse sin oxígeno a mitad de viaje es «una experiencia que no recomienda». Tú, en cambio, insistes en algo que consideras más urgente: el café.

  1. Crea dos arrays con los datos de la bodega: uno de tipo string con el nombre de cada suministro y otro de tipo int con las unidades que quedan de cada uno. El suministro de la posición 0 del primer array tiene las unidades de la posición 0 del segundo, y así sucesivamente:

    Suministro Unidades
    Agua 40
    Oxígeno 12
    Combustible 75
    Café 8
    Repuestos 20
  2. Crea una constante con el mínimo de unidades que debe haber de cada suministro (15).

  3. Crea un método que reciba un número de unidades y devuelva el texto "OK" si llega al mínimo o "RESERVA BAJA" si no llega.
  4. Recorre los arrays con un bucle for y muestra una línea por cada suministro, utilizando el método anterior.
  5. En el mismo bucle, calcula el total de unidades que hay en la bodega y cuántos suministros están en reserva baja. Muestra ambos datos al terminar.
Salida esperada
Agua: 40 unidades (OK)
Oxígeno: 12 unidades (RESERVA BAJA)
Combustible: 75 unidades (OK)
Café: 8 unidades (RESERVA BAJA)
Repuestos: 20 unidades (OK)
Total: 155 unidades
Suministros en reserva baja: 2

Reto extra

Muestra también cuál es el suministro más escaso de la bodega. (Spoiler: tú ya lo sabes.)

Pista

Como los dos arrays tienen el mismo tamaño, un único índice te sirve para leer los dos. Para el total y para el número de suministros en reserva baja necesitas dos variables declaradas antes del bucle, con valor inicial 0.

Solución
EjercicioR1.cs
using UnityEngine;

public class EjercicioR1 : MonoBehaviour
{
    const int Minimo = 15;

    void Start()
    {
        string[] suministros = new string[] {"Agua", "Oxígeno", "Combustible", "Café", "Repuestos"};
        int[] unidades = new int[] {40, 12, 75, 8, 20};

        int total = 0;
        int enReservaBaja = 0;

        for (int i = 0; i < suministros.Length; i++)
        {
            Debug.Log(suministros[i] + ": " + unidades[i] + " unidades (" + Estado(unidades[i]) + ")");

            total += unidades[i];
            if (unidades[i] < Minimo)
            {
                enReservaBaja++;
            }
        }

        Debug.Log("Total: " + total + " unidades");
        Debug.Log("Suministros en reserva baja: " + enReservaBaja);
    }

    string Estado(int cantidad)
    {
        if (cantidad >= Minimo)
        {
            return "OK";
        }
        else
        {
            return "RESERVA BAJA";
        }
    }
}

Ejercicio R-2. Indicador de combustible

int float conversiones if-else Dificultad ★☆☆

Diario de a bordo

El indicador de combustible del panel de mandos se ha fundido (juras que el café que se te cayó encima no tiene nada que ver). Mientras llega el repuesto, decides que avise el propio casco de la nave: verde si vamos bien, amarillo si conviene repostar y rojo si hay que empezar a remar.

  1. Crea dos variables de tipo int: litros, con los litros que quedan en el depósito (180), y capacidad, con los que caben (400).
  2. Calcula en una variable de tipo float el porcentaje de combustible que queda y muéstralo.
  3. Según el porcentaje, muestra un mensaje y tiñe la nave de un color:
    • Si queda el 50 % o más: «Nivel correcto» y color verde.
    • Si queda menos del 50 %, pero al menos el 20 %: «Conviene repostar» y color amarillo.
    • Si queda menos del 20 %: «¡Depósito en reserva!» y color rojo.

Teñir la nave

Para cambiar el color de la nave se modifica el color de su Sprite Renderer:

GetComponent<SpriteRenderer>().color = Color.yellow;
Salida esperada
Combustible: 45 %
Conviene repostar

Además, la nave debe verse amarilla. Prueba también con 300 y con 50 litros para comprobar los otros dos casos.

Pista

Si divides litros / capacidad con dos enteros obtendrás 0: convierte uno de los dos a float antes de dividir. En las condiciones, si compruebas primero el caso del 50 % y encadenas el resto con else if, no hace falta comprobar los dos límites de cada tramo.

Solución
EjercicioR2.cs
using UnityEngine;

public class EjercicioR2 : MonoBehaviour
{
    void Start()
    {
        int litros = 180;
        int capacidad = 400;

        float porcentaje = (float)litros / capacidad * 100;
        Debug.Log("Combustible: " + porcentaje + " %");

        if (porcentaje >= 50)
        {
            Debug.Log("Nivel correcto");
            GetComponent<SpriteRenderer>().color = Color.green;
        }
        else if (porcentaje >= 20)
        {
            Debug.Log("Conviene repostar");
            GetComponent<SpriteRenderer>().color = Color.yellow;
        }
        else
        {
            Debug.Log("¡Depósito en reserva!");
            GetComponent<SpriteRenderer>().color = Color.red;
        }
    }
}

Ejercicio R-3. Turnos de guardia

List arrays for % switch métodos Dificultad ★★☆

Diario de a bordo

El viaje hasta la próxima estrella dura una semana y alguien tiene que vigilar el puente de mando cada día. La última vez que las guardias se repartieron «a ojo», a ti te tocaron cuatro seguidas y todavía lo recuerdas en voz alta. Esta vez las repartirá el ordenador, por turnos y sin favoritismos.

  1. Crea una lista de tipo string con los nombres de tres tripulantes: Telémaco, Ulises (sí, el comandante también hace guardias) y Nono.
  2. Thais llega corriendo cuando ya estabas cerrando la lista, tarde como siempre: añádela con Add.
  3. Crea un método que reciba un número entero y devuelva el nombre del turno: "mañana" para el 0, "tarde" para el 1 y "noche" para el 2. Utiliza una sentencia switch.
  4. Recorre los 7 días del viaje con un bucle for. Cada día hace guardia el siguiente tripulante de la lista y, al llegar al último, se vuelve a empezar por el primero. Con el turno ocurre lo mismo: mañana, tarde, noche, y vuelta a empezar. Muestra una línea por cada día.
  5. Crea un array de enteros, con tantas posiciones como tripulantes, para contar cuántas guardias hace cada uno. Al terminar, muestra el recuento.
Salida esperada
Día 1: Telémaco (turno de mañana)
Día 2: Ulises (turno de tarde)
Día 3: Nono (turno de noche)
Día 4: Thais (turno de mañana)
Día 5: Telémaco (turno de tarde)
Día 6: Ulises (turno de noche)
Día 7: Nono (turno de mañana)
Guardias de Telémaco: 2
Guardias de Ulises: 2
Guardias de Nono: 2
Guardias de Thais: 1
Pista

El operador % sirve para «dar la vuelta»: si el índice del bucle va de 0 a 6, i % 4 vale 0, 1, 2, 3, 0, 1, 2. Utiliza Count en lugar de escribir el 4 a mano: así el programa seguirá funcionando si cambia el número de tripulantes.

Solución
EjercicioR3.cs
using System.Collections.Generic;
using UnityEngine;

public class EjercicioR3 : MonoBehaviour
{
    const int DiasDeViaje = 7;
    const int TurnosPorDia = 3;

    void Start()
    {
        List<string> tripulantes = new List<string>() {"Telémaco", "Ulises", "Nono"};
        tripulantes.Add("Thais");

        int[] guardias = new int[tripulantes.Count];

        for (int i = 0; i < DiasDeViaje; i++)
        {
            int tripulante = i % tripulantes.Count;
            int turno = i % TurnosPorDia;

            Debug.Log("Día " + (i + 1) + ": " + tripulantes[tripulante] + " (turno de " + NombreTurno(turno) + ")");
            guardias[tripulante]++;
        }

        for (int i = 0; i < tripulantes.Count; i++)
        {
            Debug.Log("Guardias de " + tripulantes[i] + ": " + guardias[i]);
        }
    }

    string NombreTurno(int turno)
    {
        switch (turno)
        {
            case 0:
                return "mañana";
            case 1:
                return "tarde";
            default:
                return "noche";
        }
    }
}

Ejercicio R-4. Aproximación al muelle

while float contador métodos Dificultad ★★☆

Diario de a bordo

Atracar en un muelle espacial es una maniobra delicada: la última vez que entraste «con decisión», la estación te mandó la factura de la grúa. El manual recomienda acercarse por tramos, recorriendo en cada maniobra la mitad de la distancia que falta. (Sí: haciéndolo así, en teoría, no se llega nunca. Por eso el manual también dice a partir de qué distancia se considera que has llegado.)

  1. Coloca la nave en el punto de partida, a 6 unidades a la izquierda del muelle:

    transform.position = new Vector2(-6, 0);
    
  2. Crea una variable de tipo float con la distancia que falta hasta el muelle (6) y una constante con la distancia de atraque (0.5).

  3. Crea un método que reciba la distancia que falta y devuelva lo que debe avanzar la nave: la mitad.
  4. Con un bucle while, repite la maniobra mientras la distancia que falta no sea menor que la distancia de atraque. En cada vuelta:
    • Calcula el avance con el método y mueve la nave hacia la derecha con transform.Translate(new Vector2(avance, 0));.
    • Resta el avance a la distancia que falta.
    • Muestra el número de maniobra, el avance y la distancia que falta.
  5. Al terminar, muestra cuántas maniobras han hecho falta.
Salida esperada
Maniobra 1: avanzo 3, faltan 3
Maniobra 2: avanzo 1.5, faltan 1.5
Maniobra 3: avanzo 0.75, faltan 0.75
Maniobra 4: avanzo 0.375, faltan 0.375
Atraque completado en 4 maniobras

Nota

La nave debe acabar en la posición x = -0.375, casi en el centro de la pantalla. No verás el movimiento paso a paso, porque todo el cálculo se hace en el mismo instante.

Pista

Necesitas un contador de maniobras que empiece en 0 y sume uno en cada vuelta. Recuerda que los valores float se escriben con una f al final (0.5f) y que para obtener la mitad puedes dividir entre 2f.

Solución
EjercicioR4.cs
using UnityEngine;

public class EjercicioR4 : MonoBehaviour
{
    const float DistanciaDeAtraque = 0.5f;

    void Start()
    {
        transform.position = new Vector2(-6, 0);

        float distancia = 6f;
        int maniobras = 0;

        while (distancia >= DistanciaDeAtraque)
        {
            float avance = Avance(distancia);
            transform.Translate(new Vector2(avance, 0));

            distancia -= avance;
            maniobras++;

            Debug.Log("Maniobra " + maniobras + ": avanzo " + avance + ", faltan " + distancia);
        }

        Debug.Log("Atraque completado en " + maniobras + " maniobras");
    }

    float Avance(float distancia)
    {
        return distancia / 2f;
    }
}

Ejercicio R-5. Escudos bajo fuego

while arrays && if métodos constantes Dificultad ★★☆

Diario de a bordo

Una nave pirata ha abierto fuego contra la Odiseus. El escudo absorbe los impactos mientras le queda energía; cuando se agota, lo que sobra de cada impacto lo recibe el casco. Si el casco queda demasiado dañado, Shirka ejecutará un salto de emergencia para escapar; ya lo tiene calculado «por si acaso», con un tono que no inspira ninguna confianza.

  1. Crea un array de enteros con la potencia de los impactos que va a disparar la nave pirata: 30, 12, 45, 8, 60, 25, 40 y 15.
  2. Crea dos variables de tipo int, escudo y casco, que empiecen valiendo 100.
  3. Crea una constante con el umbral de salto de emergencia (25): la nave salta en cuanto el casco queda por debajo de ese valor.
  4. Crea un método que reciba la potencia de un impacto y la energía que le queda al escudo, y devuelva la parte del impacto que llega al casco: 0 si el escudo puede absorberlo entero o, si no, la diferencia entre el impacto y el escudo.
  5. Con un bucle while, procesa los impactos uno a uno mientras queden impactos por recibir y el casco no esté por debajo del umbral. En cada vuelta:
    • Si el escudo absorbe el impacto entero, resta la potencia del impacto al escudo.
    • Si no, el escudo se queda a 0 y el casco pierde lo que haya devuelto el método.
    • Muestra el estado del escudo y del casco tras el impacto.
  6. Al salir del bucle, actúa según el motivo: si el casco ha quedado por debajo del umbral, la nave salta al hiperespacio y desaparece de la pantalla; si no, es que los piratas se han quedado sin munición.

Ocultar la nave

Para que la nave deje de verse, desactiva su Sprite Renderer:

GetComponent<SpriteRenderer>().enabled = false;
Salida esperada
Impacto de 30. Escudo: 70. Casco: 100
Impacto de 12. Escudo: 58. Casco: 100
Impacto de 45. Escudo: 13. Casco: 100
Impacto de 8. Escudo: 5. Casco: 100
Impacto de 60. Escudo: 0. Casco: 45
Impacto de 25. Escudo: 0. Casco: 20
¡Salto de emergencia! Impactos recibidos: 6 de 8

Prueba también el otro final: cambia el valor inicial del casco a 200 y comprueba que se reciben los 8 impactos, aparece el mensaje de los piratas y la nave sigue en pantalla.

Pista

La condición del while tiene dos partes unidas con &&: que el índice no se haya salido del array y que el casco aguante. No olvides sumar uno al índice en cada vuelta: al terminar, esa misma variable te dice cuántos impactos se han recibido.

Solución
EjercicioR5.cs
using UnityEngine;

public class EjercicioR5 : MonoBehaviour
{
    const int UmbralDeSalto = 25;

    void Start()
    {
        int[] impactos = new int[] {30, 12, 45, 8, 60, 25, 40, 15};
        int escudo = 100;
        int casco = 100;

        int recibidos = 0;
        while (recibidos < impactos.Length && casco >= UmbralDeSalto)
        {
            int impacto = impactos[recibidos];
            int alCasco = ImpactoEnCasco(impacto, escudo);

            if (alCasco == 0)
            {
                escudo -= impacto;
            }
            else
            {
                escudo = 0;
                casco -= alCasco;
            }

            Debug.Log("Impacto de " + impacto + ". Escudo: " + escudo + ". Casco: " + casco);
            recibidos++;
        }

        if (casco < UmbralDeSalto)
        {
            Debug.Log("¡Salto de emergencia! Impactos recibidos: " + recibidos + " de " + impactos.Length);
            GetComponent<SpriteRenderer>().enabled = false;
        }
        else
        {
            Debug.Log("Los piratas se han quedado sin munición. Casco: " + casco);
        }
    }

    int ImpactoEnCasco(int impacto, int escudo)
    {
        if (impacto <= escudo)
        {
            return 0;
        }

        return impacto - escudo;
    }
}

Ejercicio R-6. Código de acceso

string Int32.Parse arrays for % Dificultad ★★☆

Diario de a bordo

La estación de servicio solo abre sus compuertas a las naves que envían el código de acceso correcto. El código se calcula a partir de cinco números que la estación transmite por radio… como texto, para ponerlo «difícil». Nono sospecha que el sistema de seguridad lo diseñó su cuñado.

  1. Crea un array de tipo string con los textos recibidos: "12", "7", "30", "5" y "18".
  2. Recorre el array con un bucle for. En cada vuelta, convierte el texto en un número entero e indica si es par o impar.
  3. En el mismo bucle, calcula la suma de los números pares y cuenta cuántos impares hay.
  4. El código de acceso es la suma de los pares multiplicada por la cantidad de impares. Muéstralo.
Salida esperada
12 es par
7 es impar
30 es par
5 es impar
18 es par
Código de acceso: 120
Pista

Int32.Parse("52") devuelve el número 52. Para poder utilizarlo, añade using System; al principio del script. Un número es par si el resto de dividirlo entre 2 es 0.

Solución
EjercicioR6.cs
using System;
using UnityEngine;

public class EjercicioR6 : MonoBehaviour
{
    void Start()
    {
        string[] recibidos = new string[] {"12", "7", "30", "5", "18"};

        int sumaPares = 0;
        int impares = 0;

        for (int i = 0; i < recibidos.Length; i++)
        {
            int numero = Int32.Parse(recibidos[i]);

            if (numero % 2 == 0)
            {
                Debug.Log(numero + " es par");
                sumaPares += numero;
            }
            else
            {
                Debug.Log(numero + " es impar");
                impares++;
            }
        }

        int codigo = sumaPares * impares;
        Debug.Log("Código de acceso: " + codigo);
    }
}

Ejercicio R-7. Órdenes de navegación

arrays foreach switch métodos bool Dificultad ★★☆

Diario de a bordo

Shirka te ha dejado grabada la ruta para salir del puerto como una lista de órdenes: norte, sur, este y oeste. Por desgracia, apoyaste la taza encima de la consola y una de las órdenes se ha convertido en una mancha con forma de X. Las órdenes que no se entiendan, se ignoran.

  1. Crea un array de tipo string con estas órdenes: "N", "N", "E", "S", "E", "X", "O", "N" y "E".
  2. Crea un método que reciba una orden y mueva la nave una unidad con transform.Translate: hacia arriba con "N", hacia abajo con "S", hacia la derecha con "E" y hacia la izquierda con "O". Utiliza una sentencia switch. El método devolverá true si ha entendido la orden y false en cualquier otro caso.
  3. Recorre el array con foreach y llama al método con cada orden. Cuenta cuántas órdenes se han ejecutado y cuántas no; de las que no se entiendan, muestra un aviso.
  4. Al terminar, muestra los dos recuentos.

Mover la nave

transform.Translate(new Vector2(x, y)); desplaza la nave x unidades en horizontal e y en vertical. Los valores negativos la mueven hacia la izquierda y hacia abajo.

Salida esperada
Orden desconocida: X
Órdenes ejecutadas: 8
Órdenes desconocidas: 1

Nota

La nave debe acabar en la posición x = 2, y = 2.

Pista

En cada case del switch, mueve la nave y termina con return true; (así no hace falta break). En el caso default, devuelve false.

Solución
EjercicioR7.cs
using UnityEngine;

public class EjercicioR7 : MonoBehaviour
{
    void Start()
    {
        string[] ordenes = new string[] {"N", "N", "E", "S", "E", "X", "O", "N", "E"};

        int ejecutadas = 0;
        int desconocidas = 0;

        foreach (var orden in ordenes)
        {
            if (Ejecutar(orden))
            {
                ejecutadas++;
            }
            else
            {
                Debug.Log("Orden desconocida: " + orden);
                desconocidas++;
            }
        }

        Debug.Log("Órdenes ejecutadas: " + ejecutadas);
        Debug.Log("Órdenes desconocidas: " + desconocidas);
    }

    bool Ejecutar(string orden)
    {
        switch (orden)
        {
            case "N":
                transform.Translate(new Vector2(0, 1));
                return true;
            case "S":
                transform.Translate(new Vector2(0, -1));
                return true;
            case "E":
                transform.Translate(new Vector2(1, 0));
                return true;
            case "O":
                transform.Translate(new Vector2(-1, 0));
                return true;
            default:
                return false;
        }
    }
}

Ejercicio R-8. Saltos al hiperespacio

while int += constantes Dificultad ★☆☆

Diario de a bordo

El motor de salto de la Odiseus ya no es lo que era: en cada salto se calienta un poco más y gasta más combustible que en el anterior. Nono dice que es normal «a su edad». Antes de quedarte tirado en mitad de la nada, quieres saber cuántos saltos puedes dar.

  1. Crea una variable de tipo int con el combustible disponible (110) y otra con el gasto del próximo salto (10).
  2. Crea dos constantes: lo que aumenta el gasto después de cada salto (5) y los años luz que se recorren en cada salto (12).
  3. Con un bucle while, salta mientras quede combustible suficiente para el próximo salto. En cada vuelta, resta el gasto al combustible, muestra el estado y aumenta el gasto del siguiente salto.
  4. Al terminar, muestra cuántos saltos se han dado, la distancia total recorrida y el combustible sobrante.
Salida esperada
Salto 1: gasto 10, quedan 100
Salto 2: gasto 15, quedan 85
Salto 3: gasto 20, quedan 65
Salto 4: gasto 25, quedan 40
Salto 5: gasto 30, quedan 10
Saltos realizados: 5
Distancia recorrida: 60 años luz
Combustible sobrante: 10
Pista

La condición del bucle compara dos variables que cambian en cada vuelta: el combustible que queda y lo que cuesta el próximo salto. La distancia total no hace falta acumularla: basta con multiplicar al final.

Solución
EjercicioR8.cs
using UnityEngine;

public class EjercicioR8 : MonoBehaviour
{
    const int AumentoDeGasto = 5;
    const int DistanciaPorSalto = 12;

    void Start()
    {
        int combustible = 110;
        int gasto = 10;
        int saltos = 0;

        while (combustible >= gasto)
        {
            combustible -= gasto;
            saltos++;
            Debug.Log("Salto " + saltos + ": gasto " + gasto + ", quedan " + combustible);

            gasto += AumentoDeGasto;
        }

        Debug.Log("Saltos realizados: " + saltos);
        Debug.Log("Distancia recorrida: " + (saltos * DistanciaPorSalto) + " años luz");
        Debug.Log("Combustible sobrante: " + combustible);
    }
}

Ejercicio R-9. Escáner de asteroides

arrays bidimensionales for anidados string if Dificultad ★★★

Diario de a bordo

El escáner ha dividido el sector que tienes delante en una cuadrícula de 4 filas y 5 columnas, y ha marcado con un 1 las casillas en las que hay un asteroide. Tu primer impulso es cruzarlo «por donde caiga», pero Shirka te convence de buscar la fila más despejada.

  1. Crea dos constantes con el número de filas (4) y de columnas (5), y un array bidimensional de enteros con estos datos:

    int[,] sector = {
        { 0, 1, 0, 0, 1 },
        { 1, 1, 0, 1, 1 },
        { 0, 0, 0, 1, 0 },
        { 1, 0, 1, 1, 0 }
    };
    
  2. Recorre el array con dos bucles for anidados. Para cada fila, construye un texto con un # por cada asteroide y un . por cada casilla libre, y muéstralo.

  3. En el mismo recorrido, cuenta los asteroides de cada fila y el total del sector, y averigua cuál es la fila con menos asteroides.
  4. Muestra el total y la fila más despejada, y coloca la nave a la altura de esa fila. Como en el mapa las filas se cuentan de arriba abajo (la fila 0 es la de arriba), para ir a una fila la nave tiene que bajar:

    transform.position = new Vector2(0, -filaSegura);
    
Salida esperada
.#..#
##.##
...#.
#.##.
Asteroides en el sector: 10
Fila más despejada: 2

Nota

La nave debe acabar en la posición x = 0, y = -2.

Pista

El texto de cada fila y el contador de asteroides de la fila se crean dentro del primer bucle, antes de empezar el segundo: así se reinician en cada fila. Para saber cuál es la fila más despejada, guarda el menor número de asteroides que hayas visto hasta el momento; puede empezar valiendo el número de columnas más uno, que es más de lo que puede tener cualquier fila.

Solución
EjercicioR9.cs
using UnityEngine;

public class EjercicioR9 : MonoBehaviour
{
    const int Filas = 4;
    const int Columnas = 5;

    void Start()
    {
        int[,] sector = {
            { 0, 1, 0, 0, 1 },
            { 1, 1, 0, 1, 1 },
            { 0, 0, 0, 1, 0 },
            { 1, 0, 1, 1, 0 }
        };

        int total = 0;
        int filaSegura = 0;
        int minimo = Columnas + 1;

        for (int fila = 0; fila < Filas; fila++)
        {
            string texto = "";
            int enLaFila = 0;

            for (int columna = 0; columna < Columnas; columna++)
            {
                if (sector[fila, columna] == 1)
                {
                    texto += "#";
                    enLaFila++;
                }
                else
                {
                    texto += ".";
                }
            }

            Debug.Log(texto);
            total += enLaFila;

            if (enLaFila < minimo)
            {
                minimo = enLaFila;
                filaSegura = fila;
            }
        }

        Debug.Log("Asteroides en el sector: " + total);
        Debug.Log("Fila más despejada: " + filaSegura);

        transform.position = new Vector2(0, -filaSegura);
    }
}

Ejercicio R-10. El rayo reductor

while float bool métodos Dificultad ★☆☆

Diario de a bordo

La única salida del asteroide hueco en el que te has escondido es un túnel estrechísimo. Por suerte, en la última nave-supermercado compraste un rayo reductor de oferta: cada pasada deja la nave a la mitad de su tamaño. Las instrucciones no explican cómo deshacer el efecto, pero eso ya es problema de tu yo del futuro.

  1. Crea una variable de tipo float con la escala de la nave (1) y una constante con la escala máxima que cabe por el túnel (0.2).
  2. Crea un método que reciba una escala y devuelva true si la nave cabe por el túnel con esa escala, y false si no cabe.
  3. Con un bucle while, aplica el rayo mientras la nave no quepa: en cada pasada, la escala se reduce a la mitad. Muestra el número de pasada y la escala resultante.
  4. Al terminar, muestra cuántas pasadas han hecho falta y aplica la escala a la nave.

Cambiar el tamaño de la nave

El tamaño se controla con la escala: 1 es el tamaño original, 2 el doble y 0.5 la mitad. Se indica una escala para cada eje; en 2D, la del eje z se deja a 1:

transform.localScale = new Vector3(escala, escala, 1);
Salida esperada
Pasada 1: escala 0.5
Pasada 2: escala 0.25
Pasada 3: escala 0.125
La nave ya cabe por el túnel tras 3 pasadas

Además, la nave debe verse mucho más pequeña que al principio.

Pista

La condición del while puede ser directamente la llamada al método, comparada con false: «mientras Cabe(escala) sea false».

Solución
EjercicioR10.cs
using UnityEngine;

public class EjercicioR10 : MonoBehaviour
{
    const float EscalaMaxima = 0.2f;

    void Start()
    {
        float escala = 1f;
        int pasadas = 0;

        while (Cabe(escala) == false)
        {
            escala = escala / 2f;
            pasadas++;
            Debug.Log("Pasada " + pasadas + ": escala " + escala);
        }

        Debug.Log("La nave ya cabe por el túnel tras " + pasadas + " pasadas");
        transform.localScale = new Vector3(escala, escala, 1);
    }

    bool Cabe(float escala)
    {
        return escala <= EscalaMaxima;
    }
}

Ejercicio R-11. Las notas de Telémaco

arrays métodos if-else float Dificultad ★★☆

Diario de a bordo

Telémaco estudia a distancia en la academia de pilotos y acaban de llegar las notas del trimestre. Él asegura que el examen de Mecánica de motores «estaba mal planteado». Antes de decidir si le dejas tocar los mandos de la nave, quieres un resumen.

  1. Crea dos arrays con las notas, uno de tipo string con las asignaturas y otro de enteros con la nota de cada una:

    Asignatura Nota
    Navegación 7
    Mecánica de motores 4
    Astronomía 9
    Idiomas alienígenas 5
    Pilotaje 10
    Historia de la galaxia 4
  2. Crea un método que reciba una nota y devuelva su calificación: "Suspenso" por debajo de 5, "Aprobado" por debajo de 7, "Notable" por debajo de 9 y "Sobresaliente" en el resto de casos.

  3. Recorre los arrays y muestra cada asignatura con su nota y su calificación.
  4. Calcula y muestra cuántas asignaturas ha aprobado y la nota media.
  5. Si la media es de 5 o más, muestra que Telémaco supera el trimestre; si no, que tendrá que repetirlo.
Salida esperada
Navegación: 7 (Notable)
Mecánica de motores: 4 (Suspenso)
Astronomía: 9 (Sobresaliente)
Idiomas alienígenas: 5 (Aprobado)
Pilotaje: 10 (Sobresaliente)
Historia de la galaxia: 4 (Suspenso)
Asignaturas aprobadas: 4 de 6
Nota media: 6.5
Telémaco supera el trimestre

Reto extra

Si además ha sacado un sobresaliente en Pilotaje, muestra «Telémaco puede pilotar la Odiseus (bajo supervisión)». Busca la nota de Pilotaje por el nombre de la asignatura, no por su posición en el array.

Pista

En el método, comprueba los tramos de menor a mayor con if y else if: si la nota no es menor que 5 y llegas al segundo if, ya sabes que es 5 o más. Para la media, ¡cuidado con la división entera!

Solución
EjercicioR11.cs
using UnityEngine;

public class EjercicioR11 : MonoBehaviour
{
    void Start()
    {
        string[] asignaturas = new string[] {"Navegación", "Mecánica de motores", "Astronomía", "Idiomas alienígenas", "Pilotaje", "Historia de la galaxia"};
        int[] notas = new int[] {7, 4, 9, 5, 10, 4};

        int suma = 0;
        int aprobados = 0;

        for (int i = 0; i < notas.Length; i++)
        {
            Debug.Log(asignaturas[i] + ": " + notas[i] + " (" + Calificacion(notas[i]) + ")");

            suma += notas[i];
            if (notas[i] >= 5)
            {
                aprobados++;
            }
        }

        float media = (float)suma / notas.Length;

        Debug.Log("Asignaturas aprobadas: " + aprobados + " de " + notas.Length);
        Debug.Log("Nota media: " + media);

        if (media >= 5)
        {
            Debug.Log("Telémaco supera el trimestre");
        }
        else
        {
            Debug.Log("Telémaco tendrá que repetir el trimestre");
        }
    }

    string Calificacion(int nota)
    {
        if (nota < 5)
        {
            return "Suspenso";
        }
        else if (nota < 7)
        {
            return "Aprobado";
        }
        else if (nota < 9)
        {
            return "Notable";
        }
        else
        {
            return "Sobresaliente";
        }
    }
}

Ejercicio R-12. Barrido del radar

arrays for métodos return Dificultad ★★☆

Diario de a bordo

Has captado una señal de socorro, pero no sabes de dónde viene. Telémaco quiere ir a ver quién es; Shirka calcula un 73 % de probabilidades de que sea una trampa. De momento, el radar ha medido la intensidad de la señal en ocho direcciones, una cada 45 grados, y hay que orientar la nave hacia la más intensa.

  1. Crea un array de enteros con las ocho lecturas del radar: 12, 40, 7, 55, 91, 33, 60 y 18. La primera corresponde a la dirección 0°, que es hacia donde mira la nave al empezar; la segunda, a 45° en sentido antihorario; la tercera, a 90°, y así sucesivamente.
  2. Crea una constante con los grados que hay entre dos lecturas (45).
  3. Recorre el array y muestra cada dirección con su lectura.
  4. Crea un método que reciba un array de enteros y devuelva la posición en la que está el valor más alto.
  5. Utiliza el método para averiguar la dirección de la señal más intensa, muéstrala y gira la nave hacia ella.

Girar la nave

transform.Rotate(new Vector3(0, 0, grados)); gira la nave los grados indicados en sentido antihorario (o en sentido horario, si el valor es negativo).

Salida esperada
Dirección 0°: señal 12
Dirección 45°: señal 40
Dirección 90°: señal 7
Dirección 135°: señal 55
Dirección 180°: señal 91
Dirección 225°: señal 33
Dirección 270°: señal 60
Dirección 315°: señal 18
Señal más intensa: 91, en la dirección 180°

Además, la nave debe quedar girada media vuelta, mirando en sentido contrario al inicial.

Pista

La dirección de cada lectura se calcula a partir de su posición en el array: posición × 45. En el método, guarda la posición del valor más alto encontrado hasta el momento (empieza en 0) y cámbiala cuando encuentres uno mayor.

Solución
EjercicioR12.cs
using UnityEngine;

public class EjercicioR12 : MonoBehaviour
{
    const int GradosEntreLecturas = 45;

    void Start()
    {
        int[] lecturas = new int[] {12, 40, 7, 55, 91, 33, 60, 18};

        for (int i = 0; i < lecturas.Length; i++)
        {
            Debug.Log("Dirección " + (i * GradosEntreLecturas) + "°: señal " + lecturas[i]);
        }

        int posicion = PosicionDelMaximo(lecturas);
        int direccion = posicion * GradosEntreLecturas;

        Debug.Log("Señal más intensa: " + lecturas[posicion] + ", en la dirección " + direccion + "°");
        transform.Rotate(new Vector3(0, 0, direccion));
    }

    int PosicionDelMaximo(int[] valores)
    {
        int posicion = 0;

        for (int i = 1; i < valores.Length; i++)
        {
            if (valores[i] > valores[posicion])
            {
                posicion = i;
            }
        }

        return posicion;
    }
}

Ejercicio R-13. La calculadora de Shirka

métodos switch float if Dificultad ★★☆

Diario de a bordo

Shirka está otra vez de mantenimiento y, sin ella, nadie a bordo se atreve a hacer una división. Thais ha intentado dividir entre cero «para ver qué pasa» y la consola todavía echa humo. Toca programar una calculadora de emergencia que no se deje engañar.

  1. Crea un método Calcular que reciba dos números de tipo float y un texto con la operación ("+", "-", "*" o "/"), y devuelva el resultado. Utiliza una sentencia switch.
  2. El método debe evitar la división entre cero: en ese caso mostrará un aviso y devolverá 0. Hará lo mismo si recibe una operación que no conoce.
  3. Crea un segundo método, Mostrar, que reciba los mismos argumentos, llame a Calcular y muestre la operación completa con su resultado.
  4. Desde Start, prueba la calculadora con las operaciones que aparecen en la salida esperada.
Salida esperada
8 + 2 = 10
8 - 2 = 6
8 * 2 = 16
8 / 2 = 4
7 / 2 = 3.5
No se puede dividir entre cero
8 / 0 = 0
Operación desconocida: ^
8 ^ 2 = 0
Pista

Un método puede llamar a otro: Mostrar no hace ningún cálculo, solo llama a Calcular y compone el texto. Dentro del case "/" necesitas un if para comprobar si el segundo número es 0.

Solución
EjercicioR13.cs
using UnityEngine;

public class EjercicioR13 : MonoBehaviour
{
    void Start()
    {
        Mostrar(8, 2, "+");
        Mostrar(8, 2, "-");
        Mostrar(8, 2, "*");
        Mostrar(8, 2, "/");
        Mostrar(7, 2, "/");
        Mostrar(8, 0, "/");
        Mostrar(8, 2, "^");
    }

    void Mostrar(float a, float b, string operacion)
    {
        float resultado = Calcular(a, b, operacion);
        Debug.Log(a + " " + operacion + " " + b + " = " + resultado);
    }

    float Calcular(float a, float b, string operacion)
    {
        switch (operacion)
        {
            case "+":
                return a + b;
            case "-":
                return a - b;
            case "*":
                return a * b;
            case "/":
                if (b == 0)
                {
                    Debug.Log("No se puede dividir entre cero");
                    return 0;
                }
                return a / b;
            default:
                Debug.Log("Operación desconocida: " + operacion);
                return 0;
        }
    }
}

Ejercicio R-14. Luces de emergencia

Update Time.deltaTime bool if Dificultad ★★☆

Diario de a bordo

Te has quedado sin motores en mitad de una ruta comercial muy transitada. Para que nadie te embista, activas las luces de emergencia: la nave entera debe parpadear hasta que alguien te vea. Telémaco dice que parecéis un árbol de Navidad. Exactamente: esa es la idea.

Hasta ahora, todo ocurría de golpe dentro de Start. Para que algo cambie con el paso del tiempo hay que utilizar el método Update, que Unity ejecuta una vez por cada fotograma, y la variable Time.deltaTime, que contiene los segundos que han pasado desde el fotograma anterior (un número muy pequeño). Si vas sumando Time.deltaTime en una variable, tendrás un cronómetro.

  1. Crea estas variables a nivel de clase (fuera de los métodos): una pública de tipo float llamada intervalo, con valor 0.5, y otra de tipo float llamada tiempo, para el cronómetro.
  2. Crea una constante con el número de parpadeos que hará la nave (10) y una variable de clase para contarlos.
  3. En el método Update, mientras no se hayan completado todos los parpadeos:
    • Suma Time.deltaTime al cronómetro.
    • Cuando el cronómetro alcance el intervalo, cambia el estado de la nave (si se veía, se oculta; si estaba oculta, se muestra), pon el cronómetro a 0 y suma un parpadeo.
  4. Al completar el último parpadeo, muestra un mensaje en la consola.

Mostrar y ocultar la nave

GetComponent<SpriteRenderer>().enabled es un valor de tipo bool que se puede consultar y modificar: con false la nave no se dibuja y con true vuelve a verse.

Salida esperada
Luces de emergencia apagadas tras 10 parpadeos

La nave debe aparecer y desaparecer cada medio segundo y quedar visible al final. Como intervalo es pública, puedes cambiar su valor desde el inspector: prueba con 0.1 y con 1.

El inspector manda

El valor que escribes en el código al declarar una variable pública solo se usa cuando añades el script a la nave. A partir de ahí, Unity guarda el valor del inspector, y es el que se usa al pulsar play. Si después cambias el valor inicial en el código, el inspector seguirá con el antiguo. Para recuperar los valores del código, utiliza la opción Reset del menú del componente (los tres puntos de la esquina, en el inspector), o cambia el valor directamente en el inspector.

Pista

Como el número de parpadeos es par, la nave acaba en el mismo estado en el que empezó: visible. Para invertir el estado, consulta enabled con un if y asigna el valor contrario.

Solución
EjercicioR14.cs
using UnityEngine;

public class EjercicioR14 : MonoBehaviour
{
    const int TotalParpadeos = 10;

    public float intervalo = 0.5f;

    float tiempo = 0f;
    int parpadeos = 0;

    void Update()
    {
        if (parpadeos < TotalParpadeos)
        {
            tiempo += Time.deltaTime;

            if (tiempo >= intervalo)
            {
                if (GetComponent<SpriteRenderer>().enabled)
                {
                    GetComponent<SpriteRenderer>().enabled = false;
                }
                else
                {
                    GetComponent<SpriteRenderer>().enabled = true;
                }

                tiempo = 0f;
                parpadeos++;

                if (parpadeos == TotalParpadeos)
                {
                    Debug.Log("Luces de emergencia apagadas tras " + parpadeos + " parpadeos");
                }
            }
        }
    }
}

Ejercicio R-15. Patrulla de ida y vuelta

Update Time.deltaTime if variables de clase Dificultad ★★★

Diario de a bordo

La estación te ha contratado para vigilar su entrada durante un rato: hay que patrullar de un extremo a otro, ida y vuelta, tres veces. El sueldo es ridículo, pero incluye café gratis, y a ti no ha hecho falta convencerte de nada más. Al terminar, la nave se pondrá en verde para avisar de que el turno ha acabado.

Para mover la nave poco a poco, en cada fotograma hay que desplazarla una distancia muy pequeña: la velocidad (en unidades por segundo) multiplicada por Time.deltaTime. Así se mueve a la misma velocidad en cualquier ordenador.

  1. Crea dos variables públicas de tipo float: velocidad (3) y limite (4), que es la distancia máxima a la que la nave se aleja del centro, hacia cada lado.
  2. Crea una constante con el número de vueltas (3) y dos variables de clase de tipo int: sentido, que valdrá 1 cuando la nave vaya hacia la derecha y -1 cuando vaya hacia la izquierda, y vueltas, para contar las vueltas completadas.
  3. En el método Update, mientras queden vueltas por dar:
    • Mueve la nave en horizontal: transform.Translate(new Vector2(sentido * velocidad * Time.deltaTime, 0));.
    • Si la nave ha superado el límite derecho, cambia el sentido para que vuelva.
    • Si ha superado el límite izquierdo, cambia el sentido, cuenta una vuelta completada y muéstralo.
  4. Al completar la última vuelta, tiñe la nave de verde.

Consultar la posición de la nave

transform.position.x contiene la coordenada x de la nave (y transform.position.y, la coordenada y). Son valores de tipo float.

Salida esperada
Vuelta 1 completada
Vuelta 2 completada
Vuelta 3 completada
Patrulla terminada

La nave debe ir y volver tres veces entre los dos extremos y quedarse parada, de color verde, en el extremo izquierdo.

Reto extra

Cambia la forma de dar la vuelta: en lugar de cambiar el valor de sentido, gira la nave media vuelta en cada extremo con transform.Rotate y muévela siempre con el mismo Translate. ¿Por qué la nave vuelve hacia atrás si la orden de movimiento no ha cambiado? (Recuerda que Translate mueve la nave respecto a su propia orientación.)

Pista

Para detectar los extremos, compara transform.position.x con limite y con -limite. Comprueba además el sentido en cada condición (por ejemplo, «si va hacia la derecha y ha superado el límite»): así el cambio se hace una sola vez.

Solución
EjercicioR15.cs
using UnityEngine;

public class EjercicioR15 : MonoBehaviour
{
    const int TotalVueltas = 3;

    public float velocidad = 3f;
    public float limite = 4f;

    int sentido = 1;
    int vueltas = 0;

    void Update()
    {
        if (vueltas < TotalVueltas)
        {
            transform.Translate(new Vector2(sentido * velocidad * Time.deltaTime, 0));

            if (sentido == 1 && transform.position.x > limite)
            {
                sentido = -1;
            }
            else if (sentido == -1 && transform.position.x < -limite)
            {
                sentido = 1;
                vueltas++;
                Debug.Log("Vuelta " + vueltas + " completada");

                if (vueltas == TotalVueltas)
                {
                    Debug.Log("Patrulla terminada");
                    GetComponent<SpriteRenderer>().color = Color.green;
                }
            }
        }
    }
}