Ejercicios de C# · Refuerzo 1
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é.
-
Crea dos arrays con los datos de la bodega: uno de tipo
stringcon el nombre de cada suministro y otro de tipointcon 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 -
Crea una constante con el mínimo de unidades que debe haber de cada suministro (15).
- 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. - Recorre los arrays con un bucle
fory muestra una línea por cada suministro, utilizando el método anterior. - 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.
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
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.
- Crea dos variables de tipo
int:litros, con los litros que quedan en el depósito (180), ycapacidad, con los que caben (400). - Calcula en una variable de tipo
floatel porcentaje de combustible que queda y muéstralo. - 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;
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
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.
- Crea una lista de tipo
stringcon los nombres de tres tripulantes: Telémaco, Ulises (sí, el comandante también hace guardias) y Nono. - Thais llega corriendo cuando ya estabas cerrando la lista, tarde como siempre: añádela con
Add. - 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 sentenciaswitch. - 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. - Crea un array de enteros, con tantas posiciones como tripulantes, para contar cuántas guardias hace cada uno. Al terminar, muestra el recuento.
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
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.)
-
Coloca la nave en el punto de partida, a 6 unidades a la izquierda del muelle:
transform.position = new Vector2(-6, 0); -
Crea una variable de tipo
floatcon la distancia que falta hasta el muelle (6) y una constante con la distancia de atraque (0.5). - Crea un método que reciba la distancia que falta y devuelva lo que debe avanzar la nave: la mitad.
- 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.
- Calcula el avance con el método y mueve la nave hacia la derecha con
- Al terminar, muestra cuántas maniobras han hecho falta.
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
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.
- 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.
- Crea dos variables de tipo
int,escudoycasco, que empiecen valiendo 100. - Crea una constante con el umbral de salto de emergencia (25): la nave salta en cuanto el casco queda por debajo de ese valor.
- 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.
- 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.
- 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;
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
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.
- Crea un array de tipo
stringcon los textos recibidos:"12","7","30","5"y"18". - 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. - En el mismo bucle, calcula la suma de los números pares y cuenta cuántos impares hay.
- El código de acceso es la suma de los pares multiplicada por la cantidad de impares. Muéstralo.
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
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.
- Crea un array de tipo
stringcon estas órdenes:"N","N","E","S","E","X","O","N"y"E". - 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 sentenciaswitch. El método devolverátruesi ha entendido la orden yfalseen cualquier otro caso. - Recorre el array con
foreachy 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. - 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.
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
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.
- Crea una variable de tipo
intcon el combustible disponible (110) y otra con el gasto del próximo salto (10). - 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).
- 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. - Al terminar, muestra cuántos saltos se han dado, la distancia total recorrida y el combustible sobrante.
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
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.
-
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 } }; -
Recorre el array con dos bucles
foranidados. Para cada fila, construye un texto con un#por cada asteroide y un.por cada casilla libre, y muéstralo. - 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.
-
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);
.#..#
##.##
...#.
#.##.
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
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.
- Crea una variable de tipo
floatcon la escala de la nave (1) y una constante con la escala máxima que cabe por el túnel (0.2). - Crea un método que reciba una escala y devuelva
truesi la nave cabe por el túnel con esa escala, yfalsesi no cabe. - 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. - 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);
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
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.
-
Crea dos arrays con las notas, uno de tipo
stringcon 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 -
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. - Recorre los arrays y muestra cada asignatura con su nota y su calificación.
- Calcula y muestra cuántas asignaturas ha aprobado y la nota media.
- Si la media es de 5 o más, muestra que Telémaco supera el trimestre; si no, que tendrá que repetirlo.
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
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.
- 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.
- Crea una constante con los grados que hay entre dos lecturas (45).
- Recorre el array y muestra cada dirección con su lectura.
- Crea un método que reciba un array de enteros y devuelva la posición en la que está el valor más alto.
- 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).
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
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.
- Crea un método
Calcularque reciba dos números de tipofloaty un texto con la operación ("+","-","*"o"/"), y devuelva el resultado. Utiliza una sentenciaswitch. - 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.
- Crea un segundo método,
Mostrar, que reciba los mismos argumentos, llame aCalculary muestre la operación completa con su resultado. - Desde
Start, prueba la calculadora con las operaciones que aparecen en la 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
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.
- Crea estas variables a nivel de clase (fuera de los métodos): una pública de tipo
floatllamadaintervalo, con valor 0.5, y otra de tipofloatllamadatiempo, para el cronómetro. - Crea una constante con el número de parpadeos que hará la nave (10) y una variable de clase para contarlos.
- En el método
Update, mientras no se hayan completado todos los parpadeos:- Suma
Time.deltaTimeal 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.
- Suma
- 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.
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
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.
- Crea dos variables públicas de tipo
float:velocidad(3) ylimite(4), que es la distancia máxima a la que la nave se aleja del centro, hacia cada lado. - 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, yvueltas, para contar las vueltas completadas. - 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.
- Mueve la nave en horizontal:
- 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.
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
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;
}
}
}
}
}