Percursos
/
Cairo
Cairo
/
Exercícios
/
Jogo de interpretação de papéis
Jogo de interpretação de papéis

Jogo de interpretação de papéis

Exercício de aprendizagem

Introdução

Referências nulas

Se alguma vez já usaste outra linguagem de programação (C/C++, Python, Java, Ruby, Lisp, etc.), é provável que já tenhas encontrado null ou nil. O uso de null ou nil é a forma que essas linguagens têm de indicar que uma determinada variável não tem valor. No entanto, isso torna o uso acidental de uma variável null um erro fácil (e frequente) de cometer. Como podes imaginar, tentar chamar uma função que não existe, ou aceder a um valor que não existe, pode dar origem a todo o tipo de bugs e falhas. O criador de null chegou ao ponto de lhe chamar o seu 'erro de mil milhões de dólares'.

O tipo Option

Para evitar estes problemas, as variáveis de Cairo podem usar o enum Option para representar de forma segura a ausência de um valor. Este enum tem duas variantes: None, o equivalente a null em Cairo; e Some(T), em que T é um valor de qualquer tipo.

Tem este aspeto:

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

Podes pensar no Option como uma camada de segurança entre ti e os problemas que as referências nulas podem causar, mantendo ainda assim a sua utilidade conceptual.

Utilizar Option

Atribuir None a uma variável é bastante simples:

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

No entanto, se quiseres que o tipo Option transporte um valor, não podes atribuir esse valor diretamente. Uma variável do tipo Option e, por exemplo, uma variável do tipo i32 não são equivalentes. Tens de 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>

Pela mesma razão, o seguinte não vai 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 obteres o valor contido em Some, tens primeiro de verificar que ele 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 tem outras ferramentas além de match para verificar e aceder a valores contidos no Option, mas o match já te deve ser familiar.

Considera isto uma demonstração de porque é que Cairo usa Option em vez de uma referência nula. A ideia é que tens obrigatoriamente de verificar se a variável Option é Some (caso em que podes avançar e extrair e usar o valor que ela contém) ou None. Caso contrário, o teu programa não compila; o compilador protege-te do null.

Instruções

Estás a trabalhar na implementação de um RPG. A personagem do jogador é representada pelo seguinte:

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

Neste jogo, os jogadores têm de atingir o nível 10 antes de desbloquear uma reserva de mana, que podem usar para lançar feitiços. Antes disso, a mana do Player é Option::None.

Estás a trabalhar em duas funcionalidades deste jogo: o mecanismo de reviver e o mecanismo de lançar feitiços.

O método revive deve verificar se o Player está mesmo morto (a sua saúde chegou a 0) e, se estiver, o método deve devolver uma nova instância de Player com 100 de saúde. O método revive deve preservar o nível do Player. Se o nível do Player for 10 ou superior, deve ser revivido com 100 de mana. Se o nível do Player for inferior a 10, a sua mana deve 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 }

Se o método revive for chamado num Player cuja saúde seja 1 ou superior, o método deve devolver Option::None.

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

O método cast_spell recebe uma referência mutável ao Player, bem como um parâmetro mana_cost que indica quanta mana o feitiço custa. Devolve a quantidade de dano causado pelo feitiço lançado.

  • Se o jogador não tiver acesso a uma reserva de mana, tentar lançar o feitiço tem de diminuir a sua saúde no custo de mana do feitiço. O dano devolvido tem de 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);
    
  • Se o jogador tiver uma reserva de mana mas mana insuficiente, o método não deve afetar a reserva. O dano devolvido tem de 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));
    
  • Caso contrário, o feitiço é lançado com sucesso. O mana_cost deve ser deduzido da reserva de mana do Player. O dano devolvido tem de ser o dobro do custo de mana do feitiço.

    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));
    

Diverte-te!

Editar via GitHub A ligação abre numa nova janela ou separador
Cairo Exercism

Estás pronto para começar Jogo de interpretação de papéis?

Inscreve-te no Exercism para aprenderes e dominares Cairo com 25 conceitos68 exercícios, e mentoria humana real, tudo grátis.