學習軌道
/
Rust
Rust
/
練習
/
角色扮演遊戲
角色扮演遊戲

角色扮演遊戲

學習練習

簡介

空參照

如果你曾使用過其他程式語言(C/C++、Python、Java、Ruby、Lisp 等),那麼你很可能遇過null或nil。 使用null或nil,是這些語言用來表示某個變數沒有值的方式。 然而,這也讓不小心使用到指向null的變數,成為一個容易(而且經常)犯下的錯誤。 可以想見,試圖呼叫一個不存在的函式,或存取一個不存在的值,可能導致各種 bug 和當機。 null的發明者甚至將它稱為他的「十億美元錯誤」。

Option型別

為了避免這些問題,Rust 不使用空參照。 然而,它仍然需要一種安全的方式,來表示某個變數沒有值。 這就是Option派上用場的地方。 與其讓變數缺少值,Rust 的變數可以使用Option列舉。 這個列舉有兩個變體:None,等同於 Rust 的空值;以及Some(T),其中 T 是任意型別的值。

它長得像這樣:

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

你可以把Option想成一道安全防線,介於你和空參照可能引發的問題之間,同時仍保留它們在概念上的用處。

使用Option

把變數設為None相當直接:

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

不過,如果你希望Option型別帶有一個值,就不能直接指定這個值。 Option型別的變數和,比如說,i32型別的變數並不等同。 你需要使用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>

也正因如此,下面這樣也不會成功:

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

如果你想取得 Some 所含的值,你得先檢查它是否存在:

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"

除了match之外,Rust 還有其他工具可以用來檢查和存取Option裡的值,不過match現在應該已經讓你很熟悉了。

此外,也可以把這當成一個示範,說明 Rust 為什麼使用Option而不是空參照。 重點是,你_必須_檢查Option變數是Some(這種情況下你就可以放心取出並使用裡面含的值),還是None。 否則你的程式將無法編譯;編譯器會保護你免受null所害。

說明

你正在實作一款角色扮演遊戲。玩家的角色定義如下:

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

在這款遊戲中,玩家必須達到等級 10 才能解鎖魔力池,開始施放法術。在那之前,玩家的魔力是None。

你正在實作這款遊戲裡的兩項功能:復活機制和施放法術機制。

revive方法應該檢查玩家是否確實已死亡(生命值降到 0),如果是,就回傳一個生命值為 100 的新 Player 實例。 如果玩家的等級為 10 或以上,復活時也應該有 100 魔力。 如果玩家的等級低於 10,魔力應該是None。revive方法應該保留玩家的等級。

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

如果對生命值為 1 或以上的玩家呼叫revive方法,則該方法應該回傳None。

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

cast_spell方法接受一個對 Player 的可變參考,以及一個mana_cost參數,表示法術消耗多少魔力。它會回傳該法術造成的傷害量;法術成功施放時,傷害一律是法術魔力消耗的兩倍。

  • 如果玩家還無法使用魔力池,嘗試施放法術時,生命值必須減少該法術的魔力消耗量。回傳的傷害必須是 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);
    
  • 如果玩家有魔力池但魔力不足,該方法不應該影響魔力池,而是回傳 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));
    
  • 否則,應該從玩家的魔力池扣除mana_cost,並回傳對應的傷害量。

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

玩得開心!

透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Rust Exercism

準備好開始 角色扮演遊戲 了嗎?

註冊 Exercism,透過 99 個練習 和真人引導來學習並精通 Rust,全部免費。