Trilhas
/
Rust
Rust
/
Exercícios
/
Jogo de RPG
Jogo de RPG

Jogo de RPG

Exercício de aprendizagem

Introdução

Referências nulas

Se você já usou outra linguagem de programação (C/C++, Python, Java, Ruby, Lisp etc.), é provável que já tenha encontrado null ou nil antes. O uso de null ou nil é como essas linguagens indicam que uma determinada variável não tem valor. No entanto, isso faz com que usar sem querer uma variável que aponta para null seja um erro fácil (e frequente) de cometer. Como você pode imaginar, tentar chamar uma função que não existe ou acessar um valor que não existe pode levar a todo tipo de bug e travamento. O criador do null chegou a chamá-lo de seu 'erro de um bilhão de dólares'.

O tipo Option

Para evitar esses problemas, Rust não usa referências nulas. No entanto, ele ainda precisa de uma forma segura de indicar que uma determinada variável não tem valor. É aí que o Option entra. Em vez de ter uma variável sem valor, as variáveis de Rust podem usar o enum Option. Esse enum tem duas variantes: None, o equivalente ao null no Rust; e Some(T), onde T é um valor de qualquer tipo.

Ele se parece com isto:

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

Você pode pensar no Option como uma camada de segurança entre você e os problemas que as referências nulas podem causar, mantendo ainda assim a utilidade conceitual delas.

Usando Option

Definir uma variável como None é bem simples:

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

No entanto, se você quiser que o tipo Option carregue um valor, não pode atribuir esse valor diretamente. Uma variável do tipo Option e, digamos, uma variável do tipo i32 não são equivalentes. Você vai precisar usar Some:

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

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

É também por esse motivo que o seguinte não vai funcionar:

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

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

Se você quiser obter o valor contido em Some, primeiro vai precisar verificar se ele existe:

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

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

some_words = None; // some_words now set to None

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

Além do match, Rust tem outras ferramentas disponíveis para verificar e acessar valores contidos em um Option, mas o match já deve ser familiar para você a essa altura.

Além disso, encare isto como uma demonstração de por que Rust usa Option em vez de uma referência nula. O ponto é que você precisa verificar se a variável Option é Some (e nesse caso pode extrair e usar o valor contido nela) ou None. Qualquer outra coisa, e o seu programa não vai compilar; o compilador está te protegendo do null.

Instruções

Você está trabalhando na implementação de um jogo de RPG. A personagem do jogador é representada por isto:

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

Os jogadores deste jogo precisam alcançar o nível 10 para desbloquear um reservatório de mana e poder começar a lançar feitiços. Antes disso, a mana do Player é None.

Você está trabalhando 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á realmente morto (a saúde dele chegou a 0) e, se estiver, o método deve retornar uma nova instância de Player com 100 de saúde. Se o nível do Player for 10 ou mais, ele também deve ser revivido com 100 de mana. Se o nível do Player for menor que 10, a mana dele deve ser None. O método revive deve preservar o nível do Player.

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

Se o método revive for chamado em um Player cuja saúde seja 1 ou mais, o método deve retornar None.

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

O método cast_spell recebe uma referência mutável ao Player e também um parâmetro mana_cost que indica quanta mana o feitiço custa. Ele retorna a quantidade de dano que o feitiço lançado causa, que será sempre o dobro do custo de mana do feitiço se ele for lançado com sucesso.

  • Se o jogador não tiver acesso a um reservatório de mana, tentar lançar o feitiço deve diminuir a saúde dele na quantidade de mana que o feitiço custa. O dano retornado deve ser 0.

    let not_a_wizard_yet = Player { health: 79, mana: 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, None);
    
  • Se o jogador tiver um reservatório de mana, mas com mana insuficiente, o método não deve afetar o reservatório e deve retornar 0.

    let low_mana_wizard = Player { health: 93, mana: 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, Some(3));
    
  • Caso contrário, o mana_cost deve ser deduzido do reservatório de mana do Player e a quantidade de dano apropriada deve ser retornada.

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

Divirta-se!

Editar via GitHub O link abre em uma nova janela ou aba
Rust Exercism

Tudo pronto para começar Jogo de RPG?

Crie sua conta no Exercism para aprender e dominar Rust com 99 exercícios e mentoria humana de verdade, tudo de graça.