Saltar a contenido

Ejercicios de C# · Refuerzo 2

El radar de la Odiseus muestra varios contactos alrededor de la nave

Instrucciones generales

Segunda tanda de refuerzo, con las mismas reglas que la primera: los ejercicios son opcionales, no se entregan y cada uno termina con una posible solución oculta. En esta tanda cobran protagonismo las listas y las primeras clases propias.

Crea un script para cada ejercicio (EjercicioR16, EjercicioR17…) y asígnalo a la nave. Cuando un ejercicio pida una clase nueva, créala en su propio script vacío (Assets → Create → Scripting → Empty C# Script), como en la parte 4.

Recuerda que la nave debe empezar cada ejercicio en la posición (0, 0, 0), sin rotación y con escala 1. Tienes la tabla con las órdenes para moverla, girarla, ocultarla o cambiarla de color en las instrucciones de la primera tanda.

Ejercicio R-16. Lista de embarque

List Add Insert Remove Contains Sort métodos Dificultad ★☆☆

Diario de a bordo

Pusiste un anuncio ofreciendo pasaje barato en la Odiseus y se te ha ido de las manos: en el puerto espacial hay una cola que da la vuelta al muelle, y te ha parecido ver un cíclope en ella. Toca poner orden en la lista de embarque.

  1. Crea un método que reciba un título y una lista de tipo string, y muestre en una sola línea el título seguido de todos los nombres de la lista.
  2. En Start, crea una lista de pasajeros con Mentor, Circe y Néstor, y muéstrala con el método.
  3. Haz estos cambios, uno detrás de otro:
    • Añade a Calipso al final de la lista.
    • Penélope tiene pasaje preferente: insértala en la primera posición.
    • Circe ha perdido el billete: elimínala de la lista.
  4. Muestra la lista y el número de pasajeros.
  5. Comprueba si Polifemo está en la lista y muestra un mensaje con el resultado.
  6. Muestra en qué posición de la lista está Néstor.
  7. Ordena la lista alfabéticamente y muéstrala de nuevo.
Salida esperada
Lista inicial: Mentor Circe Néstor
Lista de embarque: Penélope Mentor Néstor Calipso
Pasajeros: 4
Polifemo no está en la lista (menos mal)
Néstor está en la posición 2
Lista ordenada: Calipso Mentor Néstor Penélope
Pista

Repasa en los apuntes los métodos de las listas: Add, Insert, Remove, Contains, IndexOf y Sort. En el método que muestra la lista, ve concatenando los nombres en una variable de tipo string y llama a Debug.Log una sola vez, al final.

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

public class EjercicioR16 : MonoBehaviour
{
    void Start()
    {
        List<string> pasajeros = new List<string>() {"Mentor", "Circe", "Néstor"};
        MostrarLista("Lista inicial:", pasajeros);

        pasajeros.Add("Calipso");
        pasajeros.Insert(0, "Penélope");
        pasajeros.Remove("Circe");

        MostrarLista("Lista de embarque:", pasajeros);
        Debug.Log("Pasajeros: " + pasajeros.Count);

        if (pasajeros.Contains("Polifemo"))
        {
            Debug.Log("Polifemo está en la lista");
        }
        else
        {
            Debug.Log("Polifemo no está en la lista (menos mal)");
        }

        Debug.Log("Néstor está en la posición " + pasajeros.IndexOf("Néstor"));

        pasajeros.Sort();
        MostrarLista("Lista ordenada:", pasajeros);
    }

    void MostrarLista(string titulo, List<string> lista)
    {
        string texto = titulo;

        foreach (var nombre in lista)
        {
            texto += " " + nombre;
        }

        Debug.Log(texto);
    }
}

Ejercicio R-17. Registro de la tripulación

clases constructores propiedades ToString List foreach Dificultad ★★☆

Diario de a bordo

Las autoridades del próximo puerto espacial exigen un registro de todas las personas que viajan a bordo. Hasta ahora te bastaba con una lista de nombres, pero ahora necesitas guardar más datos de cada tripulante. Y al leer la normativa descubres algo incómodo: para hacer guardia en el puente hay que ser mayor de edad. Habrá que revisar los turnos del ejercicio R-3… y explicárselo a Telémaco y a Thais. Nono, por su parte, sostiene que los robots no tienen edad.

  1. Crea un script vacío con el nombre Tripulante. La clase tendrá:
    • Tres propiedades que se puedan leer desde fuera, pero que solo pueda modificar la propia clase: Nombre (tipo string), Edad (tipo int) y Puesto (tipo string).
    • Un constructor que reciba los tres datos.
    • Un método PuedeHacerGuardia que devuelva true si el tripulante tiene 18 años o más.
    • El método ToString, que devuelva un texto como Telémaco (12 años), grumete.
  2. Crea un script EjercicioR17 y asígnalo a la nave. En el método Start, crea una lista de tripulantes y añade a estos cuatro:

    Nombre Edad Puesto
    Telémaco 12 grumete
    Ulises 41 piloto
    Nono 3 ingeniero
    Thais 11 artillera
  3. Recorre la lista con foreach y muestra los datos de cada tripulante.

  4. En el mismo bucle, calcula:
    • La edad media de la tripulación.
    • Quién es el tripulante de más edad.
    • Cuántos tripulantes pueden hacer guardia.
Salida esperada
Telémaco (12 años), grumete
Ulises (41 años), piloto
Nono (3 años), ingeniero
Thais (11 años), artillera
Edad media: 16.75 años
Veterano de la tripulación: Ulises
Pueden hacer guardia: 1
Pista

Para encontrar al de más edad, guarda en una variable de tipo Tripulante el mayor que hayas visto hasta el momento (empieza con el primero de la lista) y cámbialo cuando encuentres a otro con más edad. Para la media, ¡cuidado con la división entera!

Solución
Tripulante.cs
public class Tripulante
{
    public string Nombre { get; private set; }
    public int Edad { get; private set; }
    public string Puesto { get; private set; }

    public Tripulante(string nombre, int edad, string puesto)
    {
        Nombre = nombre;
        Edad = edad;
        Puesto = puesto;
    }

    public bool PuedeHacerGuardia()
    {
        return Edad >= 18;
    }

    public override string ToString()
    {
        return Nombre + " (" + Edad + " años), " + Puesto;
    }
}
EjercicioR17.cs
using System.Collections.Generic;
using UnityEngine;

public class EjercicioR17 : MonoBehaviour
{
    void Start()
    {
        List<Tripulante> tripulacion = new List<Tripulante>();
        tripulacion.Add(new Tripulante("Telémaco", 12, "grumete"));
        tripulacion.Add(new Tripulante("Ulises", 41, "piloto"));
        tripulacion.Add(new Tripulante("Nono", 3, "ingeniero"));
        tripulacion.Add(new Tripulante("Thais", 11, "artillera"));

        int sumaEdades = 0;
        int puedenHacerGuardia = 0;
        Tripulante veterano = tripulacion[0];

        foreach (var tripulante in tripulacion)
        {
            Debug.Log(tripulante.ToString());

            sumaEdades += tripulante.Edad;

            if (tripulante.Edad > veterano.Edad)
            {
                veterano = tripulante;
            }

            if (tripulante.PuedeHacerGuardia())
            {
                puedenHacerGuardia++;
            }
        }

        float edadMedia = (float)sumaEdades / tripulacion.Count;

        Debug.Log("Edad media: " + edadMedia + " años");
        Debug.Log("Veterano de la tripulación: " + veterano.Nombre);
        Debug.Log("Pueden hacer guardia: " + puedenHacerGuardia);
    }
}

Ejercicio R-18. La torreta de plasma

clases métodos bool for constantes Dificultad ★★☆

Diario de a bordo

Has instalado en el morro de la nave una torreta de plasma de segunda mano; el vendedor juró que solo la había usado para espantar cometas los domingos. Dispara de maravilla, pero solo le caben tres cargas y cada disparo empuja la nave un poco hacia atrás.

  1. Crea un script vacío con el nombre Torreta. La clase tendrá:
    • Una constante con la capacidad del cargador (3).
    • Una propiedad Cargas (tipo int) que solo pueda modificar la propia clase. Al crear la torreta, el cargador está lleno.
    • Un método Disparar: si quedan cargas, gasta una y devuelve true; si no quedan, devuelve false.
    • Un método Recargar, que vuelve a llenar el cargador.
  2. Crea un script EjercicioR18 y asígnalo a la nave. En el método Start, crea una torreta e intenta disparar 8 veces con un bucle for:
    • Si el disparo sale, muéstralo y haz retroceder la nave 0.2 unidades hacia la izquierda con transform.Translate.
    • Si no sale, muestra un aviso y recarga la torreta (ese intento se pierde).
  3. Al terminar, muestra cuántos disparos han salido y cuántas cargas quedan.
Salida esperada
Intento 1: ¡BOOM!
Intento 2: ¡BOOM!
Intento 3: ¡BOOM!
Intento 4: cargador vacío. Recargando...
Intento 5: ¡BOOM!
Intento 6: ¡BOOM!
Intento 7: ¡BOOM!
Intento 8: cargador vacío. Recargando...
Disparos realizados: 6
Cargas disponibles: 3

Nota

La nave debe acabar en la posición x = -1.2.

Pista

El método Disparar devuelve un bool, así que la llamada puede ir directamente dentro del if: if (torreta.Disparar()). La llamada dispara y, a la vez, te dice si el disparo ha salido.

Solución
Torreta.cs
public class Torreta
{
    const int Capacidad = 3;

    public int Cargas { get; private set; }

    public Torreta()
    {
        Cargas = Capacidad;
    }

    public bool Disparar()
    {
        if (Cargas > 0)
        {
            Cargas--;
            return true;
        }

        return false;
    }

    public void Recargar()
    {
        Cargas = Capacidad;
    }
}
EjercicioR18.cs
using UnityEngine;

public class EjercicioR18 : MonoBehaviour
{
    const int Intentos = 8;
    const float Retroceso = 0.2f;

    void Start()
    {
        Torreta torreta = new Torreta();
        int disparos = 0;

        for (int i = 1; i <= Intentos; i++)
        {
            if (torreta.Disparar())
            {
                Debug.Log("Intento " + i + ": ¡BOOM!");
                transform.Translate(new Vector2(-Retroceso, 0));
                disparos++;
            }
            else
            {
                Debug.Log("Intento " + i + ": cargador vacío. Recargando...");
                torreta.Recargar();
            }
        }

        Debug.Log("Disparos realizados: " + disparos);
        Debug.Log("Cargas disponibles: " + torreta.Cargas);
    }
}

Ejercicio R-19. Ruta de balizas

clases List foreach métodos if Dificultad ★★☆

Diario de a bordo

Para cruzar la nebulosa sin perderte tienes que pasar, en orden, por cuatro balizas de navegación. Tú aseguras que te orientas solo; la última vez que lo intentaste, acabasteis en otra galaxia. En la nebulosa los motores solo funcionan en horizontal o en vertical, así que la distancia entre dos puntos es lo que te desplazas en horizontal más lo que te desplazas en vertical.

  1. Crea un script vacío con el nombre Baliza. La clase tendrá:
    • Tres propiedades que solo pueda modificar la propia clase: Nombre (tipo string), X y Y (tipo int).
    • Un constructor que reciba los tres datos.
    • Un método DistanciaDesde que reciba las coordenadas x e y de un punto y devuelva la distancia desde ese punto hasta la baliza: la diferencia entre las x más la diferencia entre las y, las dos en positivo.
  2. Crea un script EjercicioR19 y asígnalo a la nave. En el método Start, crea una lista con estas balizas:

    Nombre X Y
    Alfa 2 1
    Beta 2 4
    Gamma -3 4
    Delta -3 -2
  3. La nave sale del punto (0, 0). Recorre la lista con foreach y, para cada baliza:

    • Calcula y muestra la distancia desde la posición actual de la nave.
    • Súmala a la distancia total.
    • Mueve la nave hasta la baliza con transform.position = new Vector2(x, y); y actualiza la posición actual.
  4. Al terminar, muestra la distancia total recorrida.
Salida esperada
Baliza Alfa (2, 1): 3 unidades
Baliza Beta (2, 4): 3 unidades
Baliza Gamma (-3, 4): 5 unidades
Baliza Delta (-3, -2): 6 unidades
Distancia total: 17 unidades

Nota

La nave debe acabar sobre la baliza Delta, en la posición x = -3, y = -2.

Pista

Una diferencia puede salir negativa (por ejemplo, de x = 2 a x = -3 hay -5). Para pasarla a positivo, escribe en la clase un método auxiliar que reciba un número y, si es menor que 0, lo devuelva cambiado de signo. Para saber dónde está la nave en cada momento, guarda sus coordenadas en dos variables de tipo int.

Solución
Baliza.cs
public class Baliza
{
    public string Nombre { get; private set; }
    public int X { get; private set; }
    public int Y { get; private set; }

    public Baliza(string nombre, int x, int y)
    {
        Nombre = nombre;
        X = x;
        Y = y;
    }

    public int DistanciaDesde(int x, int y)
    {
        return EnPositivo(X - x) + EnPositivo(Y - y);
    }

    private int EnPositivo(int numero)
    {
        if (numero < 0)
        {
            return -numero;
        }

        return numero;
    }
}
EjercicioR19.cs
using System.Collections.Generic;
using UnityEngine;

public class EjercicioR19 : MonoBehaviour
{
    void Start()
    {
        List<Baliza> ruta = new List<Baliza>();
        ruta.Add(new Baliza("Alfa", 2, 1));
        ruta.Add(new Baliza("Beta", 2, 4));
        ruta.Add(new Baliza("Gamma", -3, 4));
        ruta.Add(new Baliza("Delta", -3, -2));

        int xActual = 0;
        int yActual = 0;
        int total = 0;

        foreach (var baliza in ruta)
        {
            int distancia = baliza.DistanciaDesde(xActual, yActual);
            Debug.Log("Baliza " + baliza.Nombre + " (" + baliza.X + ", " + baliza.Y + "): " + distancia + " unidades");

            total += distancia;

            transform.position = new Vector2(baliza.X, baliza.Y);
            xActual = baliza.X;
            yActual = baliza.Y;
        }

        Debug.Log("Distancia total: " + total + " unidades");
    }
}

Ejercicio R-20. El depósito de combustible

clases propiedades get set if Dificultad ★★☆

Diario de a bordo

El ordenador de a bordo ha llegado a marcar −40 litros de combustible y, otro día, 300 litros en un depósito de 100. Shirka dice que no es culpa suya, que ella se limita a guardar lo que le dicen. Precisamente por eso hay que arreglarlo: el depósito no debería aceptar nunca un valor imposible.

  1. Crea un script vacío con el nombre Deposito. La clase tendrá:
    • Una propiedad Capacidad (tipo int) que solo pueda modificar la propia clase, y un constructor que la reciba.
    • Un campo privado litros y una propiedad pública Litros que controle el acceso a ese campo: si se le asigna un valor menor que 0, guardará 0; si se le asigna un valor mayor que la capacidad, guardará la capacidad.
    • Un método EnReserva que devuelva true si queda menos del 20 % de la capacidad.
  2. Crea un script EjercicioR20 y asígnalo a la nave. En el método Start, crea un depósito de 100 litros y haz estas operaciones, mostrando los litros después de cada una:
    • Asigna 60 litros.
    • Suma 70 litros.
    • Resta 250 litros.
    • Suma 15 litros.
  3. Al terminar, tiñe la nave de rojo si el depósito está en reserva o de verde si no lo está, y muestra un mensaje.
Salida esperada
Litros: 60
Litros: 100
Litros: 0
Litros: 15
¡Depósito en reserva!

Además, la nave debe verse roja.

Pista

Repasa en los apuntes cómo se escribe una propiedad con get y set: dentro de set, el valor que se intenta asignar se llama value. Fíjate en que deposito.Litros += 70; lee la propiedad (ejecuta get), suma 70 y asigna el resultado (ejecuta set).

Solución
Deposito.cs
public class Deposito
{
    private int litros = 0;

    public int Capacidad { get; private set; }

    public int Litros
    {
        get { return litros; }
        set
        {
            if (value < 0)
            {
                litros = 0;
            }
            else if (value > Capacidad)
            {
                litros = Capacidad;
            }
            else
            {
                litros = value;
            }
        }
    }

    public Deposito(int capacidad)
    {
        Capacidad = capacidad;
    }

    public bool EnReserva()
    {
        return litros < Capacidad * 0.2f;
    }
}
EjercicioR20.cs
using UnityEngine;

public class EjercicioR20 : MonoBehaviour
{
    void Start()
    {
        Deposito deposito = new Deposito(100);

        deposito.Litros = 60;
        Debug.Log("Litros: " + deposito.Litros);

        deposito.Litros += 70;
        Debug.Log("Litros: " + deposito.Litros);

        deposito.Litros -= 250;
        Debug.Log("Litros: " + deposito.Litros);

        deposito.Litros += 15;
        Debug.Log("Litros: " + deposito.Litros);

        if (deposito.EnReserva())
        {
            Debug.Log("¡Depósito en reserva!");
            GetComponent<SpriteRenderer>().color = Color.red;
        }
        else
        {
            Debug.Log("Nivel de combustible correcto");
            GetComponent<SpriteRenderer>().color = Color.green;
        }
    }
}

Ejercicio R-21. Sorteo de tareas

List Random.Range while RemoveAt Dificultad ★★☆

Diario de a bordo

Nadie quiere limpiar el hangar y, después de lo de la semana pasada, nadie quiere que vuelva a cocinar Thais. Para evitar discusiones, las tareas de la semana se repartirán por sorteo: cada tripulante se lleva una y nadie repite. Que la suerte decida quién desatasca el váter de gravedad cero.

  1. Crea un array de tipo string con las tareas: «Limpiar el hangar», «Cocinar», «Revisar el motor» y «Desatascar el váter».
  2. Crea una lista de tipo string con los tripulantes: Telémaco, Ulises, Nono y Thais.
  3. Recorre las tareas con un bucle while. Para cada una:
    • Elige al azar una posición de la lista de tripulantes.
    • Muestra la tarea y el tripulante al que le ha tocado.
    • Elimina a ese tripulante de la lista, para que no le pueda tocar otra tarea.

Números al azar

Random.Range(0, 4) devuelve un número entero al azar entre 0 y 3: el segundo número no está incluido.

Ejemplo de salida (cambia en cada ejecución)
Limpiar el hangar: Nono
Cocinar: Telémaco
Revisar el motor: Thais
Desatascar el váter: Ulises

Pon el juego en play varias veces y comprueba que el reparto cambia y que ningún tripulante aparece dos veces. Si a Thais le toca cocinar, puedes volver a sortear: no se lo diremos a nadie.

Pista

La lista de tripulantes se va haciendo más pequeña en cada vuelta, así que el número al azar hay que pedirlo entre 0 y Count. Para eliminar el elemento que está en una posición se utiliza RemoveAt.

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

public class EjercicioR21 : MonoBehaviour
{
    void Start()
    {
        string[] tareas = new string[] {"Limpiar el hangar", "Cocinar", "Revisar el motor", "Desatascar el váter"};
        List<string> tripulantes = new List<string>() {"Telémaco", "Ulises", "Nono", "Thais"};

        int tarea = 0;

        while (tarea < tareas.Length && tripulantes.Count > 0)
        {
            int elegido = Random.Range(0, tripulantes.Count);
            Debug.Log(tareas[tarea] + ": " + tripulantes[elegido]);

            tripulantes.RemoveAt(elegido);
            tarea++;
        }
    }
}

Ejercicio R-22. El termostato

clases constantes métodos foreach switch Dificultad ★★☆

Diario de a bordo

Tú siempre tienes calor y Nono siempre tiene frío. Os pasáis el día tocando el termostato de la nave, que ya ha estado a punto de congelar las tuberías dos veces. Telémaco ha propuesto tirar el termostato por la escotilla. Para acabar con la discusión, decides instalar uno con límites.

  1. Crea un script vacío con el nombre Termostato. La clase tendrá:
    • Tres constantes: la temperatura mínima (10), la máxima (30) y lo que cambia la temperatura en cada paso (5).
    • Una propiedad Temperatura (tipo int) que solo pueda modificar la propia clase y que empiece valiendo 20.
    • Dos métodos, Subir y Bajar, que cambien la temperatura un paso sin salirse de los límites.
    • Un método Estado que devuelva "frío" si la temperatura es menor que 15, "calor" si es mayor que 25 y "confort" en el resto de casos.
  2. Crea un script EjercicioR22 y asígnalo a la nave. En el método Start, crea un termostato y un array con estas órdenes: tres veces "subir" y, a continuación, cinco veces "bajar".
  3. Recorre el array con foreach y, con una sentencia switch, llama al método que corresponda. Después de cada orden, muestra la temperatura y el estado.
  4. Al terminar, tiñe la nave según el estado final: Color.cyan si hace frío, Color.red si hace calor y Color.white si la temperatura es de confort.
Salida esperada
subir: 25 °C (confort)
subir: 30 °C (calor)
subir: 30 °C (calor)
bajar: 25 °C (confort)
bajar: 20 °C (confort)
bajar: 15 °C (confort)
bajar: 10 °C (frío)
bajar: 10 °C (frío)

Además, la nave debe verse de color azul claro.

Pista

En Subir, comprueba antes de sumar que el resultado no supera el máximo. El estado final es un string: puedes compararlo con == o hacer otro switch para elegir el color.

Solución
Termostato.cs
public class Termostato
{
    const int Minima = 10;
    const int Maxima = 30;
    const int Paso = 5;

    public int Temperatura { get; private set; } = 20;

    public void Subir()
    {
        if (Temperatura + Paso <= Maxima)
        {
            Temperatura += Paso;
        }
    }

    public void Bajar()
    {
        if (Temperatura - Paso >= Minima)
        {
            Temperatura -= Paso;
        }
    }

    public string Estado()
    {
        if (Temperatura < 15)
        {
            return "frío";
        }
        else if (Temperatura > 25)
        {
            return "calor";
        }
        else
        {
            return "confort";
        }
    }
}
EjercicioR22.cs
using UnityEngine;

public class EjercicioR22 : MonoBehaviour
{
    void Start()
    {
        Termostato termostato = new Termostato();
        string[] ordenes = new string[] {"subir", "subir", "subir", "bajar", "bajar", "bajar", "bajar", "bajar"};

        foreach (var orden in ordenes)
        {
            switch (orden)
            {
                case "subir":
                    termostato.Subir();
                    break;
                case "bajar":
                    termostato.Bajar();
                    break;
            }

            Debug.Log(orden + ": " + termostato.Temperatura + " °C (" + termostato.Estado() + ")");
        }

        switch (termostato.Estado())
        {
            case "frío":
                GetComponent<SpriteRenderer>().color = Color.cyan;
                break;
            case "calor":
                GetComponent<SpriteRenderer>().color = Color.red;
                break;
            default:
                GetComponent<SpriteRenderer>().color = Color.white;
                break;
        }
    }
}

Ejercicio R-23. Campo de minas

arrays bidimensionales while && switch bool Dificultad ★★★

Diario de a bordo

Entre la Odiseus y la salida del sistema hay un antiguo campo de minas. Tienes un mapa de hace cien años y una ruta que te vendió un contrabandista de poco fiar, de los que no miran a los ojos. Mejor comprobar cada paso antes de darlo.

  1. Crea un array bidimensional de enteros con el mapa del campo, en el que un 1 indica una mina:

    int[,] campo = {
        { 0, 0, 0, 1, 0 },
        { 0, 1, 0, 0, 0 },
        { 1, 0, 0, 0, 1 },
        { 0, 0, 1, 1, 0 },
        { 0, 0, 0, 0, 0 }
    };
    
  2. Crea un array de tipo string con la ruta: "E", "E", "S", "S", "E", "S", "E" y "S". Con "E" se avanza una columna hacia la derecha y con "S" se baja una fila.

  3. La nave empieza en la fila 0 y la columna 0. Guarda su posición en dos variables de tipo int y crea una variable de tipo bool que indique si la nave se ha detenido.
  4. Con un bucle while, recorre la ruta mientras queden pasos y la nave no se haya detenido. En cada paso:
    • Calcula, con una sentencia switch, la fila y la columna de la casilla de destino.
    • Si en esa casilla hay una mina, muestra un aviso y detén la nave, sin moverla.
    • Si está despejada, muéstralo, actualiza la fila y la columna, y mueve la nave una unidad con transform.Translate: hacia la derecha con "E" y hacia abajo con "S".
  5. Al terminar, muestra cuántos pasos se han completado. Si la nave se ha detenido ante una mina, tíñela de rojo.
Salida esperada
Paso 1 (E): casilla (0, 1) despejada
Paso 2 (E): casilla (0, 2) despejada
Paso 3 (S): casilla (1, 2) despejada
Paso 4 (S): casilla (2, 2) despejada
Paso 5 (E): casilla (2, 3) despejada
Paso 6 (S): ¡mina en la casilla (3, 3)! Motores detenidos
Pasos completados: 5 de 8

Nota

La nave debe acabar en la posición x = 3, y = -2, de color rojo. Después, prueba con una ruta más corta que no pise ninguna mina, por ejemplo "E", "E", "S": la nave debe completar los 3 pasos y conservar su color.

Pista

Calcula primero el destino en dos variables nuevas (nuevaFila y nuevaColumna), que empiezan valiendo lo mismo que la posición actual; solo si la casilla está despejada las copias a la posición de la nave. En el array, el primer índice es la fila y el segundo, la columna.

Solución
EjercicioR23.cs
using UnityEngine;

public class EjercicioR23 : MonoBehaviour
{
    void Start()
    {
        int[,] campo = {
            { 0, 0, 0, 1, 0 },
            { 0, 1, 0, 0, 0 },
            { 1, 0, 0, 0, 1 },
            { 0, 0, 1, 1, 0 },
            { 0, 0, 0, 0, 0 }
        };
        string[] ruta = new string[] {"E", "E", "S", "S", "E", "S", "E", "S"};

        int fila = 0;
        int columna = 0;
        int completados = 0;
        bool detenida = false;

        while (completados < ruta.Length && detenida == false)
        {
            string paso = ruta[completados];
            int nuevaFila = fila;
            int nuevaColumna = columna;

            switch (paso)
            {
                case "E":
                    nuevaColumna++;
                    break;
                case "S":
                    nuevaFila++;
                    break;
            }

            if (campo[nuevaFila, nuevaColumna] == 1)
            {
                Debug.Log("Paso " + (completados + 1) + " (" + paso + "): ¡mina en la casilla (" + nuevaFila + ", " + nuevaColumna + ")! Motores detenidos");
                detenida = true;
            }
            else
            {
                Debug.Log("Paso " + (completados + 1) + " (" + paso + "): casilla (" + nuevaFila + ", " + nuevaColumna + ") despejada");

                transform.Translate(new Vector2(nuevaColumna - columna, fila - nuevaFila));
                fila = nuevaFila;
                columna = nuevaColumna;
                completados++;
            }
        }

        Debug.Log("Pasos completados: " + completados + " de " + ruta.Length);

        if (detenida)
        {
            GetComponent<SpriteRenderer>().color = Color.red;
        }
    }
}

Ejercicio R-24. El mercado espacial

clases List foreach if ToString Dificultad ★★☆

Diario de a bordo

Has atracado en un mercado espacial con 100 créditos en el bolsillo y una lista de la compra demasiado optimista. Comprarás por orden todo lo que puedas pagar. Telémaco ha escrito «chocolate» en la lista, subrayado tres veces; si no llega para el chocolate, no te lo perdonará.

  1. Crea un script vacío con el nombre Mercancia. La clase tendrá:
    • Dos propiedades que solo pueda modificar la propia clase: Nombre (tipo string) y Precio (tipo int).
    • Un constructor que reciba los dos datos.
    • El método ToString, que devuelva un texto como Agua (15 créditos).
  2. Crea un script EjercicioR24 y asígnalo a la nave. En el método Start, crea una lista con la lista de la compra, en este orden: agua (15), oxígeno (40), repuestos (30), chocolate (25) y café (10).
  3. Crea una variable con los créditos disponibles (100) y una segunda lista, vacía, para la mercancía comprada.
  4. Recorre la lista de la compra con foreach. Si los créditos llegan para pagar la mercancía, réstale el precio a los créditos y añádela a la lista de comprados; si no llegan, muestra un aviso.
  5. Al terminar, muestra cuántos productos se han comprado y los créditos que sobran.
Salida esperada
Comprado: Agua (15 créditos)
Comprado: Oxígeno (40 créditos)
Comprado: Repuestos (30 créditos)
No hay créditos para: Chocolate (25 créditos)
Comprado: Café (10 créditos)
Productos comprados: 4
Créditos restantes: 5

Reto extra

Telémaco exige su chocolate. Cambia el orden de la lista de la compra, sin tocar los precios ni los créditos, para que el chocolate se compre siempre. ¿Qué otro producto se queda sin comprar?

Pista

Al concatenar un objeto con un texto, C# llama a su método ToString automáticamente: "Comprado: " + mercancia es lo mismo que "Comprado: " + mercancia.ToString(). El número de productos comprados te lo da el Count de la segunda lista.

Solución
Mercancia.cs
public class Mercancia
{
    public string Nombre { get; private set; }
    public int Precio { get; private set; }

    public Mercancia(string nombre, int precio)
    {
        Nombre = nombre;
        Precio = precio;
    }

    public override string ToString()
    {
        return Nombre + " (" + Precio + " créditos)";
    }
}
EjercicioR24.cs
using System.Collections.Generic;
using UnityEngine;

public class EjercicioR24 : MonoBehaviour
{
    void Start()
    {
        List<Mercancia> listaDeLaCompra = new List<Mercancia>();
        listaDeLaCompra.Add(new Mercancia("Agua", 15));
        listaDeLaCompra.Add(new Mercancia("Oxígeno", 40));
        listaDeLaCompra.Add(new Mercancia("Repuestos", 30));
        listaDeLaCompra.Add(new Mercancia("Chocolate", 25));
        listaDeLaCompra.Add(new Mercancia("Café", 10));

        int creditos = 100;
        List<Mercancia> comprados = new List<Mercancia>();

        foreach (var mercancia in listaDeLaCompra)
        {
            if (mercancia.Precio <= creditos)
            {
                creditos -= mercancia.Precio;
                comprados.Add(mercancia);
                Debug.Log("Comprado: " + mercancia);
            }
            else
            {
                Debug.Log("No hay créditos para: " + mercancia);
            }
        }

        Debug.Log("Productos comprados: " + comprados.Count);
        Debug.Log("Créditos restantes: " + creditos);
    }
}

Ejercicio R-25. Radar de proximidad

clases List foreach bool && null Dificultad ★★★

Diario de a bordo

El radar se ha llenado de puntitos. Algunos son cargueros inofensivos; otros, piratas. Shirka debe localizar la nave hostil más cercana y, si está demasiado cerca, dar media vuelta y salir de allí.

  1. Crea un script vacío con el nombre Contacto. La clase tendrá tres propiedades que solo pueda modificar la propia clase, Nombre (tipo string), Distancia (tipo int, en kilómetros) y Hostil (tipo bool), y un constructor que reciba los tres datos.
  2. Crea un script EjercicioR25 y asígnalo a la nave. En el método Start, crea una lista con estos contactos:

    Nombre Distancia Hostil
    Carguero 120 no
    Pirata 80 sí
    Sonda 30 no
    Corsario 45 sí
  3. Recorre la lista con foreach. Muestra los datos de cada contacto, cuenta cuántos son hostiles y averigua cuál es el contacto hostil más cercano.

  4. Muestra el número de contactos hostiles.
  5. Crea una constante con la distancia de seguridad (50). Si hay algún contacto hostil y el más cercano está a menos de esa distancia, muestra una alerta y haz una maniobra evasiva: gira la nave 180° y, a continuación, hazla avanzar 3 unidades.

Avanzar en la dirección en la que mira la nave

transform.Translate mueve la nave respecto a su propia orientación. Por eso, si primero la giras 180° con transform.Rotate(new Vector3(0, 0, 180)); y después ejecutas transform.Translate(new Vector2(0, 3));, la nave no sube: baja.

Salida esperada
Carguero a 120 km
Pirata a 80 km (hostil)
Sonda a 30 km
Corsario a 45 km (hostil)
Contactos hostiles: 2
¡Alerta! Corsario a 45 km. Maniobra evasiva

Nota

La nave debe acabar girada media vuelta y en la posición x = 0, y = -3.

Pista

Guarda el contacto hostil más cercano en una variable de tipo Contacto que empiece valiendo null, porque al principio todavía no has encontrado ninguno. Un contacto hostil pasa a ser el más cercano si la variable todavía vale null o si su distancia es menor que la del que tenías guardado.

Solución
Contacto.cs
public class Contacto
{
    public string Nombre { get; private set; }
    public int Distancia { get; private set; }
    public bool Hostil { get; private set; }

    public Contacto(string nombre, int distancia, bool hostil)
    {
        Nombre = nombre;
        Distancia = distancia;
        Hostil = hostil;
    }
}
EjercicioR25.cs
using System.Collections.Generic;
using UnityEngine;

public class EjercicioR25 : MonoBehaviour
{
    const int DistanciaDeSeguridad = 50;

    void Start()
    {
        List<Contacto> contactos = new List<Contacto>();
        contactos.Add(new Contacto("Carguero", 120, false));
        contactos.Add(new Contacto("Pirata", 80, true));
        contactos.Add(new Contacto("Sonda", 30, false));
        contactos.Add(new Contacto("Corsario", 45, true));

        int hostiles = 0;
        Contacto masCercano = null;

        foreach (var contacto in contactos)
        {
            if (contacto.Hostil)
            {
                Debug.Log(contacto.Nombre + " a " + contacto.Distancia + " km (hostil)");
                hostiles++;

                if (masCercano == null || contacto.Distancia < masCercano.Distancia)
                {
                    masCercano = contacto;
                }
            }
            else
            {
                Debug.Log(contacto.Nombre + " a " + contacto.Distancia + " km");
            }
        }

        Debug.Log("Contactos hostiles: " + hostiles);

        if (masCercano != null && masCercano.Distancia < DistanciaDeSeguridad)
        {
            Debug.Log("¡Alerta! " + masCercano.Nombre + " a " + masCercano.Distancia + " km. Maniobra evasiva");

            transform.Rotate(new Vector3(0, 0, 180));
            transform.Translate(new Vector2(0, 3));
        }
    }
}

Ejercicio R-26. Cuenta atrás de despegue

clases Update Time.deltaTime conversiones if Dificultad ★★★

Diario de a bordo

La torre de control te ha dado permiso para despegar dentro de 5 segundos exactos. Esta vez la cuenta atrás tiene que ser de verdad: un número por segundo y, al llegar a cero, motores a toda potencia. Thais todavía se está abrochando el cinturón.

  1. Crea un script vacío con el nombre Cronometro. La clase tendrá:
    • Una propiedad Transcurrido (tipo float) que solo pueda modificar la propia clase: los segundos que han pasado desde que se creó.
    • Un método Avanzar que reciba un número de segundos y lo sume al tiempo transcurrido.
    • Un método SegundosCompletos que devuelva, como int, los segundos enteros que han transcurrido (con 2.7 segundos, devuelve 2).
  2. Crea un script EjercicioR26 y asígnalo a la nave. Tendrá una constante con la duración de la cuenta atrás (5), una variable pública velocidad (4) y dos variables de clase: el cronómetro y el último segundo que se ha mostrado.
  3. En el método Start, crea el cronómetro y muestra el primer mensaje de la cuenta atrás.
  4. En el método Update:
    • Mientras la cuenta atrás no haya terminado, haz avanzar el cronómetro con Time.deltaTime. Cada vez que cambie el número de segundos completos, muestra los segundos que faltan o, si ya no falta ninguno, el mensaje de despegue.
    • Cuando la cuenta atrás haya terminado, mueve la nave hacia arriba: transform.Translate(new Vector2(0, velocidad * Time.deltaTime));.
Salida esperada
Despegue en 5
Despegue en 4
Despegue en 3
Despegue en 2
Despegue en 1
¡Despegue!

Los mensajes deben aparecer de segundo en segundo. Tras el último, la nave debe subir hasta salir de la pantalla.

Pista

Para quedarte con la parte entera de un float, conviértelo con (int). Update se ejecuta muchas veces por segundo, así que no puedes mostrar el mensaje en todos los fotogramas: guarda en una variable el último segundo mostrado y escribe solo cuando SegundosCompletos devuelva un valor distinto.

Solución
Cronometro.cs
public class Cronometro
{
    public float Transcurrido { get; private set; }

    public void Avanzar(float segundos)
    {
        Transcurrido += segundos;
    }

    public int SegundosCompletos()
    {
        return (int)Transcurrido;
    }
}
EjercicioR26.cs
using UnityEngine;

public class EjercicioR26 : MonoBehaviour
{
    const int Duracion = 5;

    public float velocidad = 4f;

    Cronometro cronometro;
    int ultimoSegundo = 0;

    void Start()
    {
        cronometro = new Cronometro();
        Debug.Log("Despegue en " + Duracion);
    }

    void Update()
    {
        if (ultimoSegundo < Duracion)
        {
            cronometro.Avanzar(Time.deltaTime);

            if (cronometro.SegundosCompletos() != ultimoSegundo)
            {
                ultimoSegundo = cronometro.SegundosCompletos();

                if (ultimoSegundo < Duracion)
                {
                    Debug.Log("Despegue en " + (Duracion - ultimoSegundo));
                }
                else
                {
                    Debug.Log("¡Despegue!");
                }
            }
        }
        else
        {
            transform.Translate(new Vector2(0, velocidad * Time.deltaTime));
        }
    }
}

Ejercicio R-27. Luces de fiesta

arrays Update Time.deltaTime % métodos Dificultad ★★☆

Diario de a bordo

Hoy es el cumpleaños de Telémaco y en mitad del espacio no hay dónde comprar globos. Decides convertir la nave entera en una bola de discoteca: que vaya cambiando de color sin parar.

  1. Crea un array de tipo Color, como variable de clase, con estos colores: Color.red, Color.yellow, Color.green, Color.cyan y Color.magenta.
  2. Crea una variable pública de tipo float llamada intervalo (0.5) y dos variables de clase: un cronómetro y la posición del color actual.
  3. Crea un método SiguienteColor que avance a la siguiente posición del array (después de la última, vuelve a la primera) y tiña la nave con ese color.
  4. En el método Start, tiñe la nave con el primer color.
  5. En el método Update, suma Time.deltaTime al cronómetro y, cada vez que alcance el intervalo, ponlo a 0 y llama a SiguienteColor.

La nave debe cambiar de color cada medio segundo, siguiendo siempre el mismo orden y sin detenerse nunca. Prueba a añadir más colores al array: el resto del programa no debería necesitar ningún cambio.

Pista

Un array puede guardar valores de cualquier tipo, también de tipo Color. Para volver al principio al llegar al final, utiliza el operador % con el tamaño del array: (posicion + 1) % colores.Length.

Solución
EjercicioR27.cs
using UnityEngine;

public class EjercicioR27 : MonoBehaviour
{
    public float intervalo = 0.5f;

    Color[] colores = new Color[] {Color.red, Color.yellow, Color.green, Color.cyan, Color.magenta};
    float tiempo = 0f;
    int posicion = 0;

    void Start()
    {
        GetComponent<SpriteRenderer>().color = colores[posicion];
    }

    void Update()
    {
        tiempo += Time.deltaTime;

        if (tiempo >= intervalo)
        {
            tiempo = 0f;
            SiguienteColor();
        }
    }

    void SiguienteColor()
    {
        posicion = (posicion + 1) % colores.Length;
        GetComponent<SpriteRenderer>().color = colores[posicion];
    }
}

Ejercicio R-28. Piloto automático

clases List foreach switch Dificultad ★★☆

Diario de a bordo

Quieres echarte la siesta sin que la nave se quede parada. La solución es un piloto automático: una lista de maniobras que la nave ejecuta una detrás de otra, sin que nadie toque los mandos. Antes de dormirte, alguien (todo apunta a Thais) ha colado en la lista una maniobra que no existe.

  1. Crea un script vacío con el nombre Maniobra. La clase tendrá dos propiedades que solo pueda modificar la propia clase, Tipo (tipo string) y Valor (tipo float), y un constructor que reciba los dos datos.
  2. Crea un script EjercicioR28 y asígnalo a la nave. En el método Start, crea una lista con estas maniobras, en este orden:

    Tipo Valor
    avanzar 2
    girar 90
    avanzar 2
    saltar 5
    girar 90
    avanzar 2
  3. Recorre la lista con foreach y ejecuta cada maniobra con una sentencia switch:

    • "avanzar": la nave avanza las unidades indicadas en la dirección en la que mira, con transform.Translate(new Vector2(0, valor));.
    • "girar": la nave gira los grados indicados, con transform.Rotate(new Vector3(0, 0, valor));.
    • En cualquier otro caso, muestra un aviso.
  4. Al terminar, muestra cuántas maniobras se han ejecutado.
Salida esperada
Avanzar 2
Girar 90
Avanzar 2
Maniobra desconocida: saltar
Girar 90
Avanzar 2
Maniobras ejecutadas: 5

Nota

La nave debe acabar en la posición x = -2, y = 0, girada media vuelta. Recuerda que transform.Translate mueve la nave respecto a su propia orientación: por eso, después de cada giro, «avanzar» la lleva en una dirección distinta.

Pista

Dibuja el recorrido en un papel antes de ejecutar el programa: la nave sube 2 unidades, gira hacia la izquierda, avanza 2, vuelve a girar y avanza otras 2 hacia abajo.

Solución
Maniobra.cs
public class Maniobra
{
    public string Tipo { get; private set; }
    public float Valor { get; private set; }

    public Maniobra(string tipo, float valor)
    {
        Tipo = tipo;
        Valor = valor;
    }
}
EjercicioR28.cs
using System.Collections.Generic;
using UnityEngine;

public class EjercicioR28 : MonoBehaviour
{
    void Start()
    {
        List<Maniobra> plan = new List<Maniobra>();
        plan.Add(new Maniobra("avanzar", 2));
        plan.Add(new Maniobra("girar", 90));
        plan.Add(new Maniobra("avanzar", 2));
        plan.Add(new Maniobra("saltar", 5));
        plan.Add(new Maniobra("girar", 90));
        plan.Add(new Maniobra("avanzar", 2));

        int ejecutadas = 0;

        foreach (var maniobra in plan)
        {
            switch (maniobra.Tipo)
            {
                case "avanzar":
                    transform.Translate(new Vector2(0, maniobra.Valor));
                    Debug.Log("Avanzar " + maniobra.Valor);
                    ejecutadas++;
                    break;
                case "girar":
                    transform.Rotate(new Vector3(0, 0, maniobra.Valor));
                    Debug.Log("Girar " + maniobra.Valor);
                    ejecutadas++;
                    break;
                default:
                    Debug.Log("Maniobra desconocida: " + maniobra.Tipo);
                    break;
            }
        }

        Debug.Log("Maniobras ejecutadas: " + ejecutadas);
    }
}

Ejercicio R-29. El marcador

clases propiedades métodos while switch Dificultad ★★★

Diario de a bordo

Para matar el tiempo, Telémaco ha convertido el simulador de vuelo en un videojuego: se ganan puntos esquivando asteroides y derribando enemigos, y se pierde una vida con cada impacto. Te pide que programes el marcador, y ya ha avisado de que su récord, 170 puntos, es «imbatible».

  1. Crea un script vacío con el nombre Marcador. La clase tendrá:
    • Tres propiedades que solo pueda modificar la propia clase: Vidas (empieza en 3), Puntos (empieza en 0) y Nivel (empieza en 1).
    • Una constante con los puntos necesarios para subir de nivel (100).
    • Un método SumarPuntos que reciba una cantidad de puntos y la sume. Después recalculará el nivel: nivel 1 con menos de 100 puntos, nivel 2 a partir de 100, nivel 3 a partir de 200… El método devolverá true si con esos puntos se ha subido de nivel.
    • Un método PerderVida, que reste una vida sin bajar nunca de 0.
    • Un método FinDePartida, que devuelva true cuando no queden vidas.
  2. Crea un script EjercicioR29 y asígnalo a la nave. En el método Start, crea un marcador y un array con lo que ocurre en la partida: "asteroide", "enemigo", "impacto", "enemigo", "enemigo", "impacto", "asteroide", "impacto" y "enemigo".
  3. Con un bucle while, procesa los sucesos mientras queden sucesos y la partida no haya terminado. Con una sentencia switch:
    • "asteroide": suma 10 puntos.
    • "enemigo": suma 50 puntos.
    • "impacto": resta una vida.
  4. Después de cada suceso, muestra los puntos y las vidas. Si se ha subido de nivel, muéstralo también.
  5. Si la partida termina porque no quedan vidas, muestra la puntuación final y oculta la nave.
Salida esperada
asteroide: 10 puntos, vidas: 3
enemigo: 60 puntos, vidas: 3
impacto: 60 puntos, vidas: 2
enemigo: 110 puntos, vidas: 2
¡Nivel 2!
enemigo: 160 puntos, vidas: 2
impacto: 160 puntos, vidas: 1
asteroide: 170 puntos, vidas: 1
impacto: 170 puntos, vidas: 0
GAME OVER. Puntuación final: 170

Además, la nave debe desaparecer de la pantalla. Fíjate en que el último enemigo del array no llega a procesarse.

Pista

El nivel se puede calcular con una división entera: Puntos / 100 vale 0 con menos de 100 puntos, 1 a partir de 100, 2 a partir de 200… Para saber si se ha subido de nivel, guarda el nivel en una variable antes de recalcularlo y compáralo con el nuevo.

Solución
Marcador.cs
public class Marcador
{
    const int PuntosPorNivel = 100;

    public int Vidas { get; private set; } = 3;
    public int Puntos { get; private set; } = 0;
    public int Nivel { get; private set; } = 1;

    public bool SumarPuntos(int cantidad)
    {
        int nivelAnterior = Nivel;

        Puntos += cantidad;
        Nivel = Puntos / PuntosPorNivel + 1;

        return Nivel > nivelAnterior;
    }

    public void PerderVida()
    {
        if (Vidas > 0)
        {
            Vidas--;
        }
    }

    public bool FinDePartida()
    {
        return Vidas == 0;
    }
}
EjercicioR29.cs
using UnityEngine;

public class EjercicioR29 : MonoBehaviour
{
    void Start()
    {
        Marcador marcador = new Marcador();
        string[] sucesos = new string[] {"asteroide", "enemigo", "impacto", "enemigo", "enemigo", "impacto", "asteroide", "impacto", "enemigo"};

        int i = 0;

        while (i < sucesos.Length && marcador.FinDePartida() == false)
        {
            bool subeDeNivel = false;

            switch (sucesos[i])
            {
                case "asteroide":
                    subeDeNivel = marcador.SumarPuntos(10);
                    break;
                case "enemigo":
                    subeDeNivel = marcador.SumarPuntos(50);
                    break;
                case "impacto":
                    marcador.PerderVida();
                    break;
            }

            Debug.Log(sucesos[i] + ": " + marcador.Puntos + " puntos, vidas: " + marcador.Vidas);

            if (subeDeNivel)
            {
                Debug.Log("¡Nivel " + marcador.Nivel + "!");
            }

            i++;
        }

        if (marcador.FinDePartida())
        {
            Debug.Log("GAME OVER. Puntuación final: " + marcador.Puntos);
            GetComponent<SpriteRenderer>().enabled = false;
        }
    }
}

Ejercicio R-30. El semáforo de atraque

Update Time.deltaTime switch constantes Dificultad ★★★

Diario de a bordo

La estación Eolia regula la entrada a sus muelles con un semáforo, pero hoy está averiado: por lo visto, le diste un golpe al aparcar la última vez. La torre de control te dará los tiempos por radio y, para que el resto de naves sepan qué estás haciendo, la propia Odiseus hará de semáforo: esperará en rojo, calentará motores en amarillo y, al ponerse en verde, entrará en el muelle.

El programa pasa por cuatro estados, uno detrás de otro. Guarda el estado actual en una variable de clase de tipo int y, en Update, utiliza una sentencia switch para hacer en cada fotograma lo que corresponda a ese estado:

Estado Qué hace la nave Cuándo pasa al siguiente
0 Espera, de color rojo A los 2 segundos
1 Espera, de color amarillo 1 segundo después
2 De color verde, avanza hacia arriba Cuando su coordenada y supera 3
3 Se queda parada, de color blanco Nunca
  1. Crea constantes para la duración del rojo (2), la del amarillo (1) y la altura del muelle (3). Crea una variable pública velocidad (2) y dos variables de clase: el estado y un cronómetro.
  2. En el método Start, tiñe la nave de rojo y muestra el primer mensaje.
  3. En el método Update, programa el comportamiento de cada estado. Al cambiar de estado, cambia el color de la nave, muestra un mensaje y pon el cronómetro a 0.
Salida esperada
Semáforo en rojo: esperando turno
Semáforo en amarillo: calentando motores
Semáforo en verde: ¡adelante!
Atraque completado

La nave debe estar 2 segundos en rojo y 1 segundo en amarillo sin moverse; después, subir de color verde y detenerse, ya con sus colores originales, un poco por encima de y = 3.

Pista

En los estados 0 y 1, suma Time.deltaTime al cronómetro y compáralo con la duración del estado. En el estado 2 no hace falta cronómetro: mueve la nave y compara transform.position.y con la altura del muelle. El estado 3 no necesita ningún case, porque en él no ocurre nada.

Solución
EjercicioR30.cs
using UnityEngine;

public class EjercicioR30 : MonoBehaviour
{
    const float DuracionRojo = 2f;
    const float DuracionAmarillo = 1f;
    const float AlturaDelMuelle = 3f;

    public float velocidad = 2f;

    int estado = 0;
    float tiempo = 0f;

    void Start()
    {
        GetComponent<SpriteRenderer>().color = Color.red;
        Debug.Log("Semáforo en rojo: esperando turno");
    }

    void Update()
    {
        switch (estado)
        {
            case 0:
                tiempo += Time.deltaTime;
                if (tiempo >= DuracionRojo)
                {
                    estado = 1;
                    tiempo = 0f;
                    GetComponent<SpriteRenderer>().color = Color.yellow;
                    Debug.Log("Semáforo en amarillo: calentando motores");
                }
                break;
            case 1:
                tiempo += Time.deltaTime;
                if (tiempo >= DuracionAmarillo)
                {
                    estado = 2;
                    tiempo = 0f;
                    GetComponent<SpriteRenderer>().color = Color.green;
                    Debug.Log("Semáforo en verde: ¡adelante!");
                }
                break;
            case 2:
                transform.Translate(new Vector2(0, velocidad * Time.deltaTime));
                if (transform.position.y > AlturaDelMuelle)
                {
                    estado = 3;
                    GetComponent<SpriteRenderer>().color = Color.white;
                    Debug.Log("Atraque completado");
                }
                break;
        }
    }
}