閱讀本篇文章你需要有編程邏輯的基礎(chǔ),本篇文章將使用 Rust 編程語(yǔ)言的基本的條件控制語(yǔ)句和隨機(jī)數(shù)生成語(yǔ)句來(lái)實(shí)現(xiàn)一個(gè)兩位數(shù)的基礎(chǔ)數(shù)學(xué)加法計(jì)算命令行小游戲程序。這可能是 The Book 原書中的 猜數(shù)字 的演進(jìn)版本,Rust 程序會(huì)生成兩個(gè)數(shù)的數(shù)字,用戶自己則計(jì)算答案輸入進(jìn)去,然后由程序來(lái)判題。
準(zhǔn)備工作
在編寫程序之前你的電腦必須先安裝好 Rust 的基礎(chǔ)開(kāi)發(fā)環(huán)境,并且有 Cargo 支持,如果你是其他編程語(yǔ)言轉(zhuǎn)過(guò)來(lái)的例如 C、 Java 、Python 、JavaScript 、Swift 等,此程序?qū)?huì)很簡(jiǎn)單不涉及復(fù)雜的操作。首先明白什么控制臺(tái)輸出、什么變量、知道什么是數(shù)學(xué)中的四則運(yùn)算,和編程中條件控制語(yǔ)句和循環(huán)語(yǔ)句,這是看得懂本程序的前提,這個(gè)程序很簡(jiǎn)單大概 100 行代碼左右,我很經(jīng)量讓小白也能看得懂,整體的邏輯圖如下:
在編寫程序之前我們需要使用 cargo 創(chuàng)建一個(gè)名字為 math_game 的項(xiàng)目,使用命令如下:
$:?cargo?new?math_game??????????????????????????????? ?????Created?binary?(application)?`math_game`?package
到此創(chuàng)建完成了,打開(kāi)項(xiàng)目目錄下面的 src/main.rs 源文件并且開(kāi)始編寫程序主體邏輯代碼。
主體邏輯
首先我們要定義幾個(gè)全局的基礎(chǔ)變量,這些變量是幫助為我們來(lái)控制程序主體的邏輯的變量,具體的變量的作用已經(jīng)在上方的注釋中說(shuō)明了。值得注意的是 Rust 中默認(rèn)聲明一個(gè)變量是不可變的,如果想在運(yùn)行的過(guò)程中改變其值需要再變量定義的時(shí)候使用 mut 關(guān)鍵字,mut 關(guān)鍵字對(duì)應(yīng)著 Mutable 的意思,如下:
fn?main()?{ ????//?當(dāng)前回合數(shù) ????let?mut?count?=?0; ????//?需要幾輪游戲 ????let?rounds?=?10; ????//?用了存儲(chǔ)分?jǐn)?shù) ????let?mut?score?=?0; ????//?生成兩個(gè)隨機(jī)數(shù)?默認(rèn)值都為?0 ????let?mut?addend:?u32?=?0; ????let?mut?adding:?u32?=?0; }
接下來(lái)我們要定義兩個(gè)函數(shù),分別來(lái)處理隨機(jī)數(shù)生成和并將隨機(jī)數(shù)轉(zhuǎn)成數(shù)學(xué)公式展示給用戶,下面定義一個(gè)函數(shù),在 Rust 中定義一個(gè)函數(shù)要使用 fn 關(guān)鍵字。在 Rust 因?yàn)樘厥獾膬?nèi)存管理方式,所有權(quán)規(guī)則,默認(rèn)在函數(shù)之間傳遞變量可能會(huì)導(dǎo)致變量的所有權(quán)被轉(zhuǎn)移,所以這里在函數(shù)簽名中采用的是可變借用的方式,具體什么是所有權(quán)規(guī)則和借用規(guī)則這不是本篇文章的重點(diǎn),對(duì)應(yīng) Rust 初學(xué)者來(lái)說(shuō)應(yīng)該關(guān)注著怎么寫一個(gè)完整的小程序然后慢慢深入,函數(shù)體如下:
fn?next_math(addend:?&mut?u32,?adding:?&mut?u32)?->?u32?{ ????*addend?=?0; ????*adding?=?0; ????//?直接返回2個(gè)數(shù)的和方便結(jié)果判斷 ????*addend?+?*adding }
上面定義一個(gè)名為 next_math 的函數(shù),參數(shù)列表分別為外部傳入的是存儲(chǔ)被加數(shù)和加數(shù)的變量,返回值則是這兩個(gè)數(shù)的之和。目前具體的隨機(jī)數(shù)生成邏輯還沒(méi)有寫,如果需要隨機(jī)數(shù)生成這時(shí)我們就使用第三方的 Crates,什么是 Crate ?Crate 可以認(rèn)為是第三方開(kāi)發(fā)者實(shí)現(xiàn)的一些類庫(kù),類似于 Java 中的 jar 包,而 Crates 則是一個(gè)存放這些工具庫(kù)的中心倉(cāng)庫(kù),類似于 Java 中央倉(cāng)庫(kù) Maven。使用第三方的 rand 庫(kù)只需要在項(xiàng)目的根目錄中 cargo.toml 添加對(duì)應(yīng)的依賴名稱:
[dependencies] rand = "0.8.3"
添加完成之后,需要使用 cargo update 更新一下項(xiàng)目,更新完成之后如果沒(méi)有錯(cuò)誤,即可去代碼中完善 next_math 函數(shù)的邏輯,如下:
fn?next_math(addend:?&mut?u32,?adding:?&mut?u32)?->?u32?{ ????*addend?=?rand::thread_rng().gen_range(1..=100); ????*adding?=?rand::thread_rng().gen_range(1..=100); ????//?直接返回2個(gè)數(shù)的和方便結(jié)果判斷 ????*addend?+?*adding }
在函數(shù)中使用 rand::thread_rng().gen_range(1..=100) 即可生成 1 至 100 之間自然數(shù),如果需要生成更大的數(shù)字只需要調(diào)整范圍即可,最后如果不寫 return 表示最后一行的結(jié)果作為返回值,返回值類型為 u32 類型,這里返回值充當(dāng)著兩數(shù)之和并且返回給上層調(diào)用程序使用。
接下來(lái)要編寫一個(gè)提問(wèn)函數(shù) question 向用戶展示計(jì)算公式要求計(jì)算公式,傳入剛才通過(guò) next_math 函數(shù)生成的數(shù)字,并且傳入題目的索引編號(hào),如下:
//?向用戶展示計(jì)算公式要求計(jì)算公式 fn?question(addend:?&mut?u32,?adding:?&mut?u32,?index:?&mut?u32)?{ ????println!(":?第{index}題為?{addend}?+?{adding}?=???請(qǐng)輸入正確結(jié)果?",?index?=?*index?+?1,?addend?=?addend,?adding?=?adding); }
這里我們使用了 println! 這是一個(gè) Rust 內(nèi)置的宏,可以將字符串格式化輸出,在前段字符串中的 {} 則為后面所需要傳入的變量名稱占位符。接下來(lái)我們可以編寫程序主體邏輯了,要讓用戶不斷輸入信息,必須使用循環(huán)控制語(yǔ)句來(lái)編寫,在 Rust 中有多種循環(huán)語(yǔ)句,這里我們使用的是 while 循環(huán)語(yǔ)句,當(dāng)條件滿足時(shí)會(huì)進(jìn)入循環(huán)體執(zhí)行里面邏輯,當(dāng)不滿足時(shí)則跳出了循環(huán)體執(zhí)行剩下的代碼邏輯,代碼邏輯如下:
use?std::Ordering; use?rand::Rng; use?std::io; fn?main()?{ ????//?當(dāng)前回合數(shù) ????let?mut?count?=?0; ????//?需要幾輪游戲 ????let?rounds?=?10; ????//?用了存儲(chǔ)分?jǐn)?shù) ????let?mut?score?=?0; ????//?生成兩個(gè)隨機(jī)數(shù)?默認(rèn)值都為?0 ????let?mut?addend:?u32?=?0; ????let?mut?adding:?u32?=?0; ????println!(":?讓我們開(kāi)始游戲吧!?一輪游戲?yàn)?0個(gè)回合,每題10分,總分100分!"); ????//?這里我們使用?while?制造一個(gè)循環(huán) ????while?count??num, ????????????//?如果非法設(shè)置默認(rèn)值 ????????????Err(_)?=>?0, ????????}; ????????match?guess.cmp(&sum)?{ ????????????Ordering::Less?=>?{ ????????????????println!(":?你的答案太小了!正確答案是?{sum}!",?sum?=?sum); ????????????} ????????????Ordering::Greater?=>?{ ????????????????println!(":?你的答案太大了!正確答案是?{sum}!",?sum?=?sum); ????????????} ????????????Ordering::Equal?=>?{ ????????????????score?+=?10; ????????????????println!(":?恭喜你!答案正確!加10分!"); ????????????} ????????}; ????????//?添加輪數(shù)將其加一 ????????count?+=?1; ????} ????println!(":?本輪游戲結(jié)束!你的分?jǐn)?shù)為?{score}?!{exp}", ?????????????score?=?score, ?????????????exp?=?if?score?>=?60 ?????????????{?"成績(jī)合格!"?} ?????????????else ?????????????{?"成績(jī)不合格!"?}) } fn?next_math(addend:?&mut?u32,?adding:?&mut?u32)?->?u32?{ ????*addend?=?rand::thread_rng().gen_range(1..=100); ????*adding?=?rand::thread_rng().gen_range(1..=100); ????//?直接返回2個(gè)數(shù)的和方便結(jié)果判斷 ????*addend?+?*adding } //?向用戶展示計(jì)算公式要求計(jì)算結(jié)構(gòu) fn?question(addend:?&mut?u32,?adding:?&mut?u32,?index:?&mut?u32)?{ ????println!(":?第{index}題為?{addend}?+?{adding}?=???請(qǐng)輸入正確結(jié)果?",?index?=?*index?+?1,?addend?=?addend,?adding?=?adding); }
這里跟要關(guān)注的是主體代碼邏輯里面函數(shù)作用,要讓用戶輸入信息那就必須使用系統(tǒng)調(diào)用從控制臺(tái)中讀入輸入的數(shù)據(jù),使用兩段代碼邏輯完成,如下:
?
?
????????//?用來(lái)存儲(chǔ)用戶控制臺(tái)輸入的變量 ????????let?mut?guess?=?String::new(); ????????//?從控制臺(tái)上讀入輸入字符串 ????????io::stdin() ????????????.read_line(&mut?guess) ????????????.expect(":?你能不能好好輸!請(qǐng)輸入正數(shù)!");
第一段中的 guess 為用來(lái)存儲(chǔ)臨時(shí)用戶輸入的字符串信息,第二段邏輯則是通過(guò)標(biāo)準(zhǔn)庫(kù)中的 io 包從標(biāo)準(zhǔn)輸入中讀入一行輸入數(shù)據(jù),而 expect 則防止用戶輸入的信息是非法,如果非法程序就提示該錯(cuò)誤信息,非常容易理解。
????????//?解析輸入的值 ????????let?guess:?u32?=?match?guess.trim().parse()?{ ????????????Ok(num)?=>?num, ????????????//?如果非法設(shè)置默認(rèn)值 ????????????Err(_)?=>?0, ????????}; ????????match?guess.cmp(&sum)?{ ????????????Ordering::Less?=>?{ ????????????????println!(":?你的答案太小了!正確答案是?{sum}!",?sum?=?sum); ????????????} ????????????Ordering::Greater?=>?{ ????????????????println!(":?你的答案太大了!正確答案是?{sum}!",?sum?=?sum); ????????????} ????????????Ordering::Equal?=>?{ ????????????????score?+=?10; ????????????????println!(":?恭喜你!答案正確!加10分!"); ????????????} ????????};
最后我們得到用戶輸入的字符串,這時(shí)我們需要將字符串轉(zhuǎn)成數(shù)字,將字符串轉(zhuǎn)成數(shù)字的方式有很多種,這里使用的具有 Rust 特性的模式匹配的方式,如果字符串轉(zhuǎn)換成功將執(zhí)行 Ok() 返回正常的數(shù)字,而如果字符串不能成功轉(zhuǎn)換成數(shù)字則會(huì)走 Err(_) ,這里如果轉(zhuǎn)換錯(cuò)誤就使用默認(rèn)值 0 作為用戶輸入的結(jié)果。
最后我們?cè)俅问褂昧四J狡ヅ?,將用戶輸入的結(jié)果和計(jì)算機(jī)計(jì)算的結(jié)果進(jìn)行比對(duì),在程序的頭部我們?cè)黾恿肆硪粋€(gè) use 聲明,從標(biāo)準(zhǔn)庫(kù)引入了一個(gè)叫做 std::Ordering 的類型到作用域中。Ordering 也是一個(gè)枚舉,不過(guò)它的成員是 Less、Greater 和 Equal。這是比較兩個(gè)值時(shí)可能出現(xiàn)的三種結(jié)果,接著,底部的五行新代碼使用了 Ordering 類型,cmp 方法用來(lái)比較兩個(gè)值并可以在任何可比較的值上調(diào)用。它獲取一個(gè)被比較值的引用:這里是把 guess 與 sum 做比較。然后它會(huì)返回一個(gè)剛才通過(guò) use 引入作用域的 Ordering 枚舉的成員。使用一個(gè) match 表達(dá)式,根據(jù)對(duì) guess 和 sum 調(diào)用 cmp 返回的 Ordering 成員來(lái)決定接下來(lái)做什么。
有了上面的解釋就很容易明白這段代碼的邏輯了,用戶輸入正確的答案就將分?jǐn)?shù)加 10 分,如果不正??梢愿嬖V時(shí)候離準(zhǔn)確的答案的差值或者大小,這里的使用的大小,最后完成整個(gè)程序的邏輯并且將游戲輪數(shù)加 1 回合,當(dāng)循環(huán)體執(zhí)行完成打印分?jǐn)?shù)信息并且展示到控制臺(tái)中。
?println!(
????????":?本輪游戲結(jié)束!你的分?jǐn)?shù)為?{score}?!{exp}", ????????score?=?score, ????????exp?=?if?score?>=?60?{ ????????????"成績(jī)合格!" ????????}?else?{ ????????????"成績(jī)不合格!" ????????} ????)
因?yàn)?Rust 沒(méi)有三目表達(dá)式,這里我采用的是if 語(yǔ)句塊來(lái)控制具體的分?jǐn)?shù)合格信息的輸出。
小 結(jié)
至此我們就完成一個(gè) Rust 小游戲的編寫工作,代碼其實(shí)一點(diǎn)都不復(fù)雜,主要是將思路轉(zhuǎn)換成具體代碼實(shí)現(xiàn)。
編輯:黃飛
?
評(píng)論