我認為學習計算機語言,應該先用后學,這一節,我們來實現一個猜數字的小游戲。
先簡單介紹一個這個游戲的內容:游戲先生成一個1到100之間的任意一個數字,然后我們輸入自己猜測的數字,游戲會告訴我們輸入的數字太大還是太小,然后我們重新輸入新的數字,直到猜到游戲生成的數字,然后游戲結束。
創建項目
制作游戲的第一步先創建項目,創建方法和上一節一樣,使用 cargo 來創建一個名為 guessing_game 的項目。
cargo new guessing_game && cd guessing_game
項目創建完成,可以運行一下,如果程序打印出 Hello, World! 則證明程序創建完成,運行命令如下:
cargo run
讀取猜測的數字
正式寫游戲的第一步,讓游戲先讀取我們猜測的數字。我們可以先把打印語句換成提示我們輸入數字的提示語句。
use std::io;
fn main() {
println!("猜測數字游戲,請輸入您猜測的數字。");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("讀取數字失敗!");
println!("您猜測的數字是:{}", guess);
}
這段代碼包含了大量的信息,我們一行一行地過一遍。
1.因為我們需要讀取用戶的輸入,然后把它作為結果打印出來,所以需要把 標准庫(被稱作 std )中的 io 依賴引入當前作用域。
2.在主函數中寫方法體,首先是打印提示語,不說了。
3.然后創建一個用於保存即將輸入的字符串的 String 類型的變量 guess。
4.把控制台輸入的數字讀取到變量 guess 中,如果讀取失敗,則打印 “讀取數字失敗!” 的字符串。
5.把讀取的數字再打印到控制台。
注:這段程序的細節暫時先不深究了,后續文章會一一解釋清楚。
測試一下這段程序:
cargo run
Compiling guessing_game v0.1.0 (/Users/shanpengfei/work/rust-work-space/study/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 1.01s
Running `target/debug/guessing_game`
猜測數字游戲,請輸入您猜測的數字。
2
您猜測的數字是:2
生成隨機數
我們的游戲需要創建一個隨機數,供我們去猜測,這個數字要求每次啟動游戲時都是不相同的,這樣游戲才更加有意思。接下來我們在游戲中生成一個1到100的隨機數。但是 rust 沒有在它的標准庫中提供生成隨機數的方法,不過沒關系,它提供了生成隨機數的名為 rand 的 crate。我們來引入一下生成隨機數的 crate,修改 Cargo.toml 文件:
[dependencies]
rand = "^0.3.14"
只需要在 [dependencies] 下面添加需要的 crate 即可。這次添加的 crate 名字是 rand,版本號 0.3.14, 而 ^
的意思是兼容 0.3.14 版本的任何版本都可以。然后我們編譯一下程序,就會自動下載引入的依賴:
cargo build
Updating crates.io index
Compiling libc v0.2.65
Compiling rand v0.4.6
Compiling rand v0.3.23
Compiling guessing_game v0.1.0 (/Users/shanpengfei/work/rust-work-space/study/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 1m 13s
引入了生成隨機數和 crate 后,我們來生成一下需要的 crate,代碼如下:
use std::io;
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("生成的隨機數字是:{}", secret_number);
println!("猜測數字游戲,請輸入您猜測的數字。");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("讀取數字失敗!");
println!("您猜測的數字是:{}", guess);
}
可以看到我們在前面代碼的基礎上添加了三行代碼:
1.第一行是引入生成隨機數的依賴。
2.第二行是生成一個隨機數,隨機數的范圍是 [1, 101),區間是左閉右開,說人話就是1到100。
3.第三行是打印生成的隨機數。
然后我們測試一下添加的隨機數是否生效:
cargo run
Compiling guessing_game v0.1.0 (/Users/shanpengfei/work/rust-work-space/study/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.45s
Running `target/debug/guessing_game`
生成的隨機數字是:79
猜測數字游戲,請輸入您猜測的數字。
6
您猜測的數字是:6
比較隨機數和猜測數
現在我們可以輸入自己猜測的數字,也可以生成隨機數字了,那么接下來就是比較二者的大小了。但是在比較之前還有個問題,控制台輸入的數字是 string 類型的,而隨機生成的數字是無符號32位整型(u32),二者不類型不一致,不能作比較,因此,在比較之前,我們應該先把控制台輸入的 string 類型的數字轉成u32類型的,代碼如下:
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("生成的隨機數字是:{}", secret_number);
println!("猜測數字游戲,請輸入您猜測的數字。");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("讀取數字失敗!");
let guess: u32 = guess.trim().parse().expect("請輸入一個數字!");
println!("您猜測的數字是:{}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("您猜測的數字太小了!"),
Ordering::Greater => println!("您猜測的數字太大了!"),
Ordering::Equal => println!("恭喜您,猜對了!"),
}
}
可見,我們在三個位置添加了代碼:
1.從標准庫中添加了比較的依賴。
2.把輸入的數字類型成u32類型,如果輸入的不是數字,則轉換失敗,打印出錯誤信息。
3.最后一部分就是比較一下二者的大小,並打印出比較的結果。
好了,我們先測試一下吧,這里我們只測正確的輸入:
cargo run 101 ↵
Compiling guessing_game v0.1.0 (/Users/shanpengfei/work/rust-work-space/study/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.42s
Running `target/debug/guessing_game`
生成的隨機數字是:53
猜測數字游戲,請輸入您猜測的數字。
4
您猜測的數字是:4
您猜測的數字太小了!
添加循環
我們發現,我們只輸入了一次,游戲就結束了,這顯然不符合我們的預期。我們的預期是,我們可以一直猜一直猜,直到猜中才讓游戲結束,那應該怎么修改一下呢?添加一個循環,代碼如下:
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("生成的隨機數字是:{}", secret_number);
loop {
println!("猜測數字游戲,請輸入您猜測的數字。");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("讀取數字失敗!");
let guess: u32 = guess.trim().parse().expect("請輸入一個數字!");
println!("您猜測的數字是:{}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("您猜測的數字太小了!"),
Ordering::Greater => println!("您猜測的數字太大了!"),
Ordering::Equal => println!("恭喜您,猜對了!"),
}
}
}
這里修改得比較簡單,只需要添加一個名叫 loop 的關鍵字,然后把需要循環的內容放在 {} 中即可,然后我們測試一下:
cargo run
Compiling guessing_game v0.1.0 (/Users/shanpengfei/work/rust-work-space/study/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.38s
Running `target/debug/guessing_game`
生成的隨機數字是:71
猜測數字游戲,請輸入您猜測的數字。
50
您猜測的數字是:50
您猜測的數字太小了!
猜測數字游戲,請輸入您猜測的數字。
71
您猜測的數字是:71
恭喜您,猜對了!
猜測數字游戲,請輸入您猜測的數字。
45
您猜測的數字是:45
您猜測的數字太小了!
猜測數字游戲,請輸入
t
thread 'main' panicked at '請輸入一個數字!: ParseIntError { kind: InvalidDigit }', src/libcore/result.rs:1165:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
我們的游戲可以多次輸入了,但是有沒有發現一些問題呢?
1.游戲直接告訴我們生成的數字了,那就不用猜了,直接輸入就好了。
2.當我們猜對后,游戲沒有結束。
3.當我們輸入的內容不是數字的時候,才會結束游戲,而且不僅打印了我們預期的錯誤信息,還打印了其它信息。
接下來,我們把這些問題依次修改,代碼如下:
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1, 101);
// println!("生成的隨機數字是:{}", secret_number);
loop {
println!("猜測數字游戲,請輸入您猜測的數字。");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("讀取數字失敗!");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("您猜測的數字是:{}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("您猜測的數字太小了!"),
Ordering::Greater => println!("您猜測的數字太大了!"),
Ordering::Equal => {
println!("恭喜您,猜對了!");
break;
}
}
}
}
這三處錯誤的修改方式依次是:
1.把打印隨機數的代碼注釋掉。
2.在做類型轉換時,使用 match 關鍵字作判斷,如果轉化成功,則返回轉化后的結果,如果轉化失敗,不管因為什么原因失敗,都直接跳出本次循環。
3.在做二個數字大小判斷時,如果判斷相等,則結束循環。
我們來測試一下修改的結果:
cargo run
Compiling guessing_game v0.1.0 (/Users/shanpengfei/work/rust-work-space/study/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.38s
Running `target/debug/guessing_game`
猜測數字游戲,請輸入您猜測的數字。
50
您猜測的數字是:50
您猜測的數字太小了!
猜測數字游戲,請輸入您猜測的數字。
r
猜測數字游戲,請輸入您猜測的數字。
75
您猜測的數字是:75
您猜測的數字太小了!
猜測數字游戲,請輸入您猜測的數字。
87
您猜測的數字是:87
您猜測的數字太大了!
猜測數字游戲,請輸入您猜測的數字。
81
您猜測的數字是:81
恭喜您,猜對了!
可以看到我們的游戲制作完成了~~
歡迎閱讀單鵬飛的學習筆記