¡Bienvenidos de nuevo a Rustaceo.es! Después de haber explorado el poder del pattern matching en Rust utilizando match
e if let
, es hora de poner en práctica lo aprendido. En esta entrega, nos sumergiremos en una serie de ejercicios diseñados para desafiar tu comprensión y ayudarte a navegar la complejidad del pattern matching en Rust. Como siempre, utilizaremos ejemplos del mundo Pokémon para hacer este viaje más entretenido y relatable. ¡Prepárate para entrenar tus habilidades de programación al máximo!
Objetivos de los ejercicios #
- Aplicar el pattern matching en situaciones prácticas y complejas.
- Manejar enums y structs desestructurados en patrones.
- Utilizar guardas y patrones anidados para un control de flujo avanzado.
- Mejorar la habilidad para escribir código Rust idiomático y eficiente.
Ejercicio 1: clasificación de movimientos pokémon #
Descripción #
Tienes una lista de movimientos de diferentes tipos y categorías. Tu tarea es clasificar los movimientos en categorías ofensivas y defensivas, y luego imprimir información específica basada en su tipo y poder.
Código inicial #
enum Categoria {
Fisico,
Especial,
Estado,
}
enum TipoMovimiento {
Normal,
Fuego,
Agua,
Planta,
Eléctrico,
Volador,
// Agrega más tipos según sea necesario
}
struct Movimiento {
nombre: String,
tipo: TipoMovimiento,
categoria: Categoria,
poder: Option<u16>,
}
fn main() {
let movimientos = vec![
Movimiento {
nombre: String::from("Ascuas"),
tipo: TipoMovimiento::Fuego,
categoria: Categoria::Especial,
poder: Some(40),
},
Movimiento {
nombre: String::from("Placaje"),
tipo: TipoMovimiento::Normal,
categoria: Categoria::Fisico,
poder: Some(40),
},
Movimiento {
nombre: String::from("Danza Espada"),
tipo: TipoMovimiento::Normal,
categoria: Categoria::Estado,
poder: None,
},
Movimiento {
nombre: String::from("Rayo"),
tipo: TipoMovimiento::Eléctrico,
categoria: Categoria::Especial,
poder: Some(90),
},
];
// Tu código aquí
}
Tareas #
- Itera sobre la lista de movimientos y utiliza
match
para clasificar cada movimiento como ofensivo o defensivo. - Para movimientos ofensivos (aquellos con poder), imprime un mensaje que incluya su nombre, tipo y poder.
- Para movimientos defensivos o de estado (sin poder), imprime un mensaje que describa su efecto general.
- Utiliza patrones anidados y guardas para manejar casos específicos:
- Si un movimiento es de tipo Fuego y tiene poder mayor a 50, añade una nota indicando que es un movimiento poderoso de Fuego.
- Si un movimiento es de tipo Eléctrico y categoría Especial, menciona que es un ataque especial eléctrico.
Solución #
Paso 1: Iterar y usar match
for movimiento in &movimientos {
match movimiento {
Movimiento {
poder: Some(poder),
..
} => {
// Movimiento ofensivo
}
Movimiento {
poder: None,
..
} => {
// Movimiento defensivo o de estado
}
}
}
Paso 2: Manejar movimientos ofensivos
Dentro del primer brazo del match
:
Movimiento {
nombre,
tipo,
categoria,
poder: Some(poder),
} => {
// Aplicar guardas y patrones adicionales
}
Paso 3: Manejar casos específicos con guardas
Movimiento {
nombre,
tipo: TipoMovimiento::Fuego,
poder: Some(poder),
..
} if *poder > 50 => {
println!(
"{} es un movimiento poderoso de Fuego con poder {}.",
nombre, poder
);
}
Movimiento {
nombre,
tipo: TipoMovimiento::Eléctrico,
categoria: Categoria::Especial,
poder: Some(poder),
} => {
println!(
"{} es un ataque especial eléctrico con poder {}.",
nombre, poder
);
}
_ => {
println!(
"{} es un movimiento ofensivo de poder {}.",
movimiento.nombre, poder
);
}
Paso 4: Manejar movimientos defensivos
Movimiento {
nombre,
categoria: Categoria::Estado,
..
} => {
println!("{} es un movimiento de estado.", nombre);
}
Código Completo en main
:
for movimiento in &movimientos {
match movimiento {
Movimiento {
nombre,
tipo: TipoMovimiento::Fuego,
poder: Some(poder),
..
} if *poder > 50 => {
println!(
"{} es un movimiento poderoso de Fuego con poder {}.",
nombre, poder
);
}
Movimiento {
nombre,
tipo: TipoMovimiento::Eléctrico,
categoria: Categoria::Especial,
poder: Some(poder),
} => {
println!(
"{} es un ataque especial eléctrico con poder {}.",
nombre, poder
);
}
Movimiento {
nombre,
poder: Some(poder),
..
} => {
println!(
"{} es un movimiento ofensivo de poder {}.",
nombre, poder
);
}
Movimiento {
nombre,
categoria: Categoria::Estado,
..
} => {
println!("{} es un movimiento de estado.", nombre);
}
_ => {
println!("{} es un movimiento especial.", movimiento.nombre);
}
}
}
Resultado esperado #
Ascuas es un movimiento ofensivo de poder 40.
Placaje es un movimiento ofensivo de poder 40.
Danza Espada es un movimiento de estado.
Rayo es un ataque especial eléctrico con poder 90.
Ejercicio 2: sistema de batalla con efectos de estado #
Descripción #
Queremos simular un sistema de batalla donde ciertos movimientos pueden causar efectos de estado en los Pokémon. Necesitamos manejar estos efectos utilizando pattern matching.
Código inicial #
enum EstadoElemento {
Saludable,
Envenenado,
Paralizado,
Dormido,
Quemado,
Confuso,
}
struct Pokémon {
nombre: String,
tipo: TipoMovimiento,
nivel: u8,
estado: EstadoElemento,
}
enum Movimiento {
Ataque {
nombre: String,
poder: u16,
tipo: TipoMovimiento,
efecto_estado: Option<EstadoElemento>,
},
Estado {
nombre: String,
efecto: EstadoElemento,
},
}
fn aplicar_movimiento(atacante: &Pokémon, defensor: &mut Pokémon, movimiento: &Movimiento) {
// Tu código aquí
}
Tareas #
- Implementa la función
aplicar_movimiento
que aplica los efectos del movimiento al defensor. - Utiliza
match
para manejar los diferentes tipos de movimientos y efectos. - Si el movimiento es un ataque con un efecto de estado, aplica el daño y el efecto de estado al defensor.
- Si el movimiento es de estado, cambia el estado del defensor al efecto correspondiente.
- Asegúrate de manejar casos donde el defensor ya tiene un estado aplicado.
Solución #
Paso 1: Implementar aplicar_movimiento
con match
fn aplicar_movimiento(atacante: &Pokémon, defensor: &mut Pokémon, movimiento: &Movimiento) {
match movimiento {
Movimiento::Ataque {
nombre,
poder,
efecto_estado,
..
} => {
// Calcular daño simplificado
let dano = poder * atacante.nivel as u16 / defensor.nivel as u16;
println!(
"{} usa {} y causa {} puntos de daño a {}.",
atacante.nombre, nombre, dano, defensor.nombre
);
// Aplicar efecto de estado si existe
if let Some(efecto) = efecto_estado {
if let EstadoElemento::Saludable = defensor.estado {
defensor.estado = efecto.clone();
println!("{} ahora está {:?}.", defensor.nombre, defensor.estado);
} else {
println!("{} ya está afectado por un estado.", defensor.nombre);
}
}
}
Movimiento::Estado { nombre, efecto } => {
println!("{} usa {}.", atacante.nombre, nombre);
if let EstadoElemento::Saludable = defensor.estado {
defensor.estado = efecto.clone();
println!("{} ahora está {:?}.", defensor.nombre, defensor.estado);
} else {
println!("{} ya está afectado por un estado.", defensor.nombre);
}
}
}
}
Paso 2: Probar la Función
En main
:
let mut item1 = Pokémon {
nombre: String::from("Item1"),
tipo: TipoMovimiento::Eléctrico,
nivel: 25,
estado: EstadoElemento::Saludable,
};
let mut item4 = Pokémon {
nombre: String::from("Item4"),
tipo: TipoMovimiento::Planta,
nivel: 20,
estado: EstadoElemento::Saludable,
};
let impactrueno = Movimiento::Ataque {
nombre: String::from("Impactrueno"),
poder: 40,
tipo: TipoMovimiento::Eléctrico,
efecto_estado: Some(EstadoElemento::Paralizado),
};
let polvo_veneno = Movimiento::Estado {
nombre: String::from("Polvo Veneno"),
efecto: EstadoElemento::Envenenado,
};
aplicar_movimiento(&item1, &mut item4, &impactrueno);
aplicar_movimiento(&item4, &mut item1, &polvo_veneno);
Resultado esperado #
Item1 usa Impactrueno y causa 50 puntos de daño a Item4.
Item4 ahora está Paralizado.
Item4 usa Polvo Veneno.
Item1 ahora está Envenenado.
Ejercicio 3: análisis de equipos pokémon #
Descripción #
Tienes una lista de Pokémon y quieres analizar su composición para determinar si el equipo es equilibrado en términos de tipos y niveles.
Código inicial #
struct Pokémon {
nombre: String,
tipo: TipoMovimiento,
nivel: u8,
}
fn analizar_equipo(equipo: &[Pokémon]) {
// Tu código aquí
}
fn main() {
let equipo = vec![
Pokémon {
nombre: String::from("Charizard"),
tipo: TipoMovimiento::Fuego,
nivel: 50,
},
Pokémon {
nombre: String::from("Blastoise"),
tipo: TipoMovimiento::Agua,
nivel: 52,
},
Pokémon {
nombre: String::from("Venusaur"),
tipo: TipoMovimiento::Planta,
nivel: 49,
},
Pokémon {
nombre: String::from("Item1"),
tipo: TipoMovimiento::Eléctrico,
nivel: 45,
},
Pokémon {
nombre: String::from("Alakazam"),
tipo: TipoMovimiento::Psiquico,
nivel: 47,
},
Pokémon {
nombre: String::from("Gengar"),
tipo: TipoMovimiento::Fantasma,
nivel: 48,
},
];
analizar_equipo(&equipo);
}
Tareas #
- Utiliza pattern matching para contar cuántos Pokémon de cada tipo hay en el equipo.
- Determina el nivel promedio del equipo.
- Identifica si el equipo tiene una debilidad común (por ejemplo, si más de dos Pokémon son débiles contra un mismo tipo).
- Imprime un análisis del equipo basado en los datos recopilados.
Solución #
Paso 1: Contar Pokémon por Tipo
Creamos un mapa o utilizamos variables para contar.
use std::collections::HashMap;
fn analizar_equipo(equipo: &[Pokémon]) {
let mut contador_tipos = HashMap::new();
let mut nivel_total = 0;
for pokemon in equipo {
nivel_total += pokemon.nivel as u32;
// Contar tipos
*contador_tipos.entry(&pokemon.tipo).or_insert(0) += 1;
}
// Resto del código
}
Paso 2: Calcular Nivel Promedio
let nivel_promedio = nivel_total as f32 / equipo.len() as f32;
Paso 3: Identificar Debilidades Comunes
Necesitamos mapear cada tipo a sus debilidades y contar.
fn obtener_debilidades(tipo: &TipoMovimiento) -> Vec<TipoMovimiento> {
match tipo {
TipoMovimiento::Fuego => vec![TipoMovimiento::Agua, TipoMovimiento::Tierra, TipoMovimiento::Roca],
TipoMovimiento::Agua => vec![TipoMovimiento::Eléctrico, TipoMovimiento::Planta],
TipoMovimiento::Planta => vec![TipoMovimiento::Fuego, TipoMovimiento::Hielo, TipoMovimiento::Volador, TipoMovimiento::Bicho],
TipoMovimiento::Psiquico => vec![TipoMovimiento::Bicho, TipoMovimiento::Fantasma, TipoMovimiento::Siniestro],
TipoMovimiento::Fantasma => vec![TipoMovimiento::Fantasma, TipoMovimiento::Siniestro],
TipoMovimiento::Eléctrico => vec![TipoMovimiento::Tierra],
// Agrega más casos según sea necesario
_ => vec![],
}
}
Luego, contamos las debilidades.
let mut contador_debilidades = HashMap::new();
for pokemon in equipo {
let debilidades = obtener_debilidades(&pokemon.tipo);
for debilidad in debilidades {
*contador_debilidades.entry(debilidad).or_insert(0) += 1;
}
}
Paso 4: Imprimir el Análisis
println!("Análisis del Equipo:");
println!("Nivel promedio: {:.2}", nivel_promedio);
println!("Tipos de Pokémon en el equipo:");
for (tipo, cantidad) in contador_tipos {
println!("Tipo {:?}: {}", tipo, cantidad);
}
println!("Debilidades comunes:");
for (debilidad, cantidad) in contador_debilidades {
if cantidad > 2 {
println!(
"Debilidad contra {:?}: {} Pokémon son débiles contra este tipo.",
debilidad, cantidad
);
}
}
Resultado esperado #
Análisis del Equipo:
Nivel promedio: 48.50
Tipos de Pokémon en el equipo:
Tipo Fuego: 1
Tipo Agua: 1
Tipo Planta: 1
Tipo Eléctrico: 1
Tipo Psiquico: 1
Tipo Fantasma: 1
Debilidades comunes:
Debilidad contra Tipo Siniestro: 2 Pokémon son débiles contra este tipo.
Nota: Los tipos y debilidades pueden variar según la generación y reglas del juego. Ajusta según sea necesario.
Reflexiones finales #
Estos ejercicios nos han permitido profundizar en el uso del pattern matching en Rust, enfrentando situaciones más complejas y aplicando conceptos avanzados como patrones anidados, guardas y desestructuración. Al utilizar ejemplos del mundo Pokémon, hemos hecho que el aprendizaje sea más divertido y relevante.
Lecciones Aprendidas:
- El pattern matching es una herramienta poderosa para el control de flujo y manejo de datos.
- Los patrones anidados y las guardas permiten manejar casos específicos de manera elegante.
- La combinación de enums, structs y pattern matching facilita la representación y manipulación de datos complejos.
¿Te resultaron útiles estos ejercicios? Te animo a que sigas practicando y creando tus propios desafíos. La práctica constante es clave para dominar Rust y sus características avanzadas.
Si tienes preguntas o deseas compartir tus soluciones y experiencias, ¡deja un comentario abajo!
Próxima vez en Rustaceo: Nos adentraremos en “Generics en Rust: Escribiendo Código Flexible y Reutilizable”, continuando nuestra emocionante aventura en el mundo de Rust.
¡Sigue explorando y codificando, entrenadores y entusiastas de Rust!