Tracks
/
Cairo
Cairo
/
Ejercicios
/
Juego de rol
Juego de rol

Juego de rol

Ejercicio de aprendizaje

Introducción

Referencias nulas

Si alguna vez has usado otro lenguaje de programación (C/C++, Python, Java, Ruby, Lisp, etc.), es probable que ya te hayas topado con null o nil. El uso de null o nil es la forma en que estos lenguajes indican que una variable en particular no tiene valor. Sin embargo, esto hace que usar una variable null por accidente sea un error fácil (y frecuente) de cometer. Como puedes imaginar, intentar llamar a una función que no está ahí, o acceder a un valor que no existe, puede provocar todo tipo de bugs y cierres inesperados. El creador de null llegó al punto de llamarlo su «error de mil millones de dólares».

El tipo Option

Para evitar estos problemas, las variables de Cairo pueden usar el enum Option para representar de forma segura la ausencia de un valor. Este enum tiene dos variantes: None, el equivalente a null en Cairo; y Some(T), donde T es un valor de cualquier tipo.

Se ve así:

enum Option<T> {
    None,
    Some(T),
}

Puedes pensar en Option como una capa de seguridad entre tú y los problemas que pueden causar las referencias nulas, sin dejar de conservar su utilidad conceptual.

Cómo usar Option

Asignar None a una variable es bastante sencillo:

let nothing: Option<u32> = Option::None;  // Variable nothing is set to None

Sin embargo, si quieres que el tipo Option contenga un valor, no puedes asignar ese valor directamente. Una variable de tipo Option y, digamos, una variable de tipo i32 no son equivalentes. Necesitas usar Some:

let wrong_way: Option<i32> = -4; // This will not work

let right_way: Option<i32> = Option::Some(-4); // This will work
let another_right_way = Option::Some(-4); // Compiler infers that this is Option<i32>

Por la misma razón, lo siguiente no funcionará:

let number = 47;
let option_number = Option::Some(15);

let compile_error = number + option_number; // Cannot add an i32 and an Option<i32> - they are of different types

Para obtener el valor que contiene Some, primero debes comprobar que existe:

let mut some_words = Option::Some("choose something to say"); // some_words set to something

match some_words {
    Option::Some(str) => println!("Here, we will {}", str),
    Option::None => println!("I've got nothing to say"),
} // Prints, "Here, we will choose something to say"

some_words = Option::None; // some_words now set to None

// Exactly the same match block as above
match some_words {
    Option::Some(str) => println!("Here, we will {}", str),
    Option::None => println!("I've got nothing to say"),
} // Prints, "I've got nothing to say"

Cairo tiene otras herramientas además de match para comprobar y acceder a los valores que contiene Option, pero a estas alturas match ya debería resultarte familiar.

Considera esto una demostración de por qué Cairo usa Option en lugar de una referencia nula. El punto es que tienes que comprobar si la variable Option es Some (en cuyo caso puedes seguir adelante y extraer y usar el valor que contiene) o None. De lo contrario, tu programa no compilará; el compilador te protege de null.

Instrucciones

Estás trabajando en la implementación de un juego de rol. El personaje del jugador está representado por lo siguiente:

pub struct Player {
    health: u32,
    mana: Option<u32>,
    level: u32,
}

Los jugadores de este juego deben alcanzar el nivel 10 antes de desbloquear una reserva de maná, que pueden usar para lanzar hechizos. Antes de ese punto, el maná del jugador es Option::None.

Estás trabajando en dos funcionalidades de este juego: la mecánica de reanimación y la mecánica de lanzamiento de hechizos.

El método revive debe verificar que el jugador esté realmente muerto (su salud llegó a 0) y, si lo está, debe devolver una nueva instancia de Player con 100 de salud. El método revive debe conservar el nivel del jugador. Si el nivel del jugador es 10 o más, debe reanimarse con 100 de maná. Si el nivel del jugador es menor que 10, su maná debe ser Option::None.

let dead_player = Player { health: 0, mana: Option::None, level: 2 };
dead_player.revive()
// Returns Player { health: 100, mana: Option::None, level: 2 }

Si se llama al método revive sobre un jugador cuya salud es 1 o más, el método debe devolver Option::None.

let alive_player = Player { health: 1, mana: Option::Some(15), level: 11 };
alive_player.revive()
// Returns Option::None

El método cast_spell recibe una referencia mutable al jugador y también un parámetro mana_cost que indica cuánto maná cuesta el hechizo. Devuelve la cantidad de daño que produce el hechizo lanzado.

  • Si el jugador no tiene acceso a una reserva de maná, intentar lanzar el hechizo debe reducir su salud en el costo de maná del hechizo. El daño devuelto debe ser 0.

    let not_a_wizard_yet = Player { health: 79, mana: Option::None, level: 9 };
    assert_eq!(not_a_wizard_yet.cast_spell(5), 0)
    assert_eq!(not_a_wizard_yet.health, 74);
    assert_eq!(not_a_wizard_yet.mana, Option::None);
    
  • Si el jugador tiene una reserva de maná pero maná insuficiente, el método no debe afectar la reserva. El daño devuelto debe ser 0.

    let low_mana_wizard = Player { health: 93, mana: Option::Some(3), level: 12 };
    assert_eq!(low_mana_wizard.cast_spell(10), 0);
    assert_eq!(low_mana_wizard.health, 93);
    assert_eq!(low_mana_wizard.mana, Option::Some(3));
    
  • En caso contrario, el hechizo se lanza con éxito. El mana_cost debe deducirse de la reserva de maná del jugador. El daño devuelto debe ser el doble del costo de maná del hechizo.

    let wizard = Player { health: 123, mana: Option::Some(30), level: 18 };
    assert_eq!(wizard.cast_spell(10), 20);
    assert_eq!(wizard.health, 123);
    assert_eq!(wizard.mana, Option::Some(20));
    

¡Diviértete!

Editar en GitHub El enlace se abre en una ventana o una pestaña nuevas
Cairo Exercism

¿Todo listo para empezar Juego de rol?

Regístrate en Exercism para aprender y dominar Cairo con 25 conceptos68 ejercicios y mentoría humana real, todo gratis.