2章 数当てゲームのプログラミング#
2章で扱う内容はこちら
- 数当てゲームの実装からRustについて知っていく
新規プロジェクトの立ち上げ#
まずは,新規プロジェクトの立ち上げをします.できたらCargo runもしてみましょう.
$ cargo new guessing_game
$ cd guessing_game予想を処理する#
標準入力の実装をしてみましょう.src/main.rsのなかみをまずは以下のように書きます.“use std::io"を宣言することで標準入出力に関するライブラリをインポートできます.変数に値を格納するには,“let"を使います.Rustでは変数はデフォルトで不変(immutable)なので,一度変数宣言をしたら変更はできません.C++でいうconstのことですかね.しかし,“mut"をつけることで変数を可変(mutable)にできます.変数guessはString型のインスタンスを返します.”::“構文はnewがStringの関連関数であることを示します.関連関数はある型に対して実装される関数のことです.
“.read_line(&mut guess)“は標準入力を受け取るメソッドです.”&“は参照であり,これもmutで可変にしておく必要があります.“read_line"メソッドはResult値も返します.これは,エラー処理に関する列挙型の値で,".expect"で例外処理をしているのはこのためです.
use std::io;
fn main() {
println!("Guess the number!");
println!("Please input your guess.");
let mut guess = String::new(); // 変数に値を格納する
io::stdin() // ユーザの入力を受け取る
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}println!()中には”{}“に変数名を入れることで値を呼び出すことができます.式の評価結果を表示するときは空の”{}“を置き,式をカンマ区切りでリストにして続けます.
let x = 5;
let y = 10;
println!("x = {x}");
println!("y+2 = { y+2 }"); // <- コレはエラーが出る
println!("y+2 = {}", y+2);ここまでで,実行してみましょう.
$ cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 6.44s
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
6
You guessed: 6秘密の数字を生成する#
乱数を使用して秘密の数字を生成します.標準ライブラリには乱数の機能はないため,“rand"クレートを使用して機能を追加します.Cargo.tomlファイルにrandクレートを依存関係に含めます.
[dependencies]
rand = "0.8.5"main.rsは変更せずに,一度ビルドしてみましょう.dependenciesに追加した依存関係をCargoはレジストリから取得します.なお,一度取得すると次からは再利用されます.
$ cargo build
Updating crates.io index
Locking 14 packages to latest Rust 1.94.1 compatible versions
Adding cfg-if v1.0.4
Adding getrandom v0.2.17
Adding libc v0.2.185
Adding ppv-lite86 v0.2.21
Adding proc-macro2 v1.0.106
Adding quote v1.0.45
Adding rand v0.8.6 (available: v0.10.1)
Adding rand_chacha v0.3.1
Adding rand_core v0.6.4
Adding syn v2.0.117
Adding unicode-ident v1.0.24
Adding wasi v0.11.1+wasi-snapshot-preview1
Adding zerocopy v0.8.48
Adding zerocopy-derive v0.8.48
Downloaded cfg-if v1.0.4
Downloaded rand_chacha v0.3.1
Downloaded ppv-lite86 v0.2.21
Downloaded getrandom v0.2.17
Downloaded rand_core v0.6.4
Downloaded rand v0.8.6
Downloaded libc v0.2.185
Downloaded zerocopy v0.8.48
Downloaded 8 crates (1.2MiB) in 0.13s
Compiling libc v0.2.185
Compiling zerocopy v0.8.48
Compiling cfg-if v1.0.4
Compiling getrandom v0.2.17
Compiling rand_core v0.6.4
Compiling ppv-lite86 v0.2.21
Compiling rand_chacha v0.3.1
Compiling rand v0.8.6
Compiling guessing_game v0.1.0 (/home/icn/project/guessing_game)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.14sクレートを更新して,アップグレードするときは"update"コマンドを使います.もし,randクレートが新しいバージョンとして0.8.6と0.9.0の二つがリリースされていたならば,以下のようになるでしょう.デフォルトでは0.8.5以上,0.9.0未満のバージョンのみを検索するため,こうなります.ただ,明示的にtomlファイルに0.9.0と変更すればそうなります.
$ cargo update
Updating crates.io index
(crates.ioインデックスを更新しています)
Updating rand v0.8.5 -> v0.8.6
(randクレートをv0.8.5 -> v0.8.6に更新しています)話を戻して,乱数生成を行います.main.rsを以下のように更新しましょう.変数secret_numberが生成した乱数を格納するものです.
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}"); //ココは最終的には隠す
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}予想と秘密の数字を比較する#
数値を比較しましょう.以下のコードを実行してみるとどうなるでしょうか.実は,このままだとコンパイルエラーとなってしまいます.
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}"); //ココは最終的には隠す
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}“std::cmp::Ordering"型はLess,Greater,Equalという3つの列挙子をもっています.“cmp"メソッドを使って二つの比較を行い,Ordering型の列挙子を返すようにします.“match"式は複数のアームで構成されており,マッチさせるパターンと与えられた値がマッチしたときに実行されます.例えば,秘密の数字が40で予想した数字が30だとしたら,Ordering::Lessがマッチするので,“Too small"が返ってきます.
ここで,コンパイル結果を見てみましょう.このエラーは型の不一致です.guessはString型なのに対して,secret_numberは整数型です.文字列と整数は比較できません.
$ cargo build
Compiling libc v0.2.86
Compiling getrandom v0.2.2
Compiling cfg-if v1.0.0
Compiling ppv-lite86 v0.2.10
Compiling rand_core v0.6.2
Compiling rand_chacha v0.3.0
Compiling rand v0.8.5
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
error[E0308]: mismatched types
--> src/main.rs:22:21
|
22 | match guess.cmp(&secret_number) {
| --- ^^^^^^^^^^^^^^ expected `&String`, found `&{integer}`
| |
| arguments to this method are incorrect
|
= note: expected reference `&String`
found reference `&{integer}`
note: method defined here
--> /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/cmp.rs:814:8
For more information about this error, try `rustc --explain E0308`.
error: could not compile `guessing_game` (bin "guessing_game") due to 1 previous errorそこで,標準入力を得た後に,以下の行を追加します.trimメソッドは文字列の先頭と末尾の空白を削除します.また,parseメソッドは文字列を別の型に変更します.今,guess: u32としているため,符号なし32ビット整数に変換しようとしています.parseメソッドを使うときは,コンパイラが解析できる文字列にしておく必要があります.例えば,“A👍%“を文字列から数値にしようなどと言ってもできません.
let guess: u32 = guess.trim().parse().expect("Please type a number!");ループで複数回の予想を可能にする#
loopキーワードはfor文のような役割をもちます.main.rsを以下のように変更してみましょう.これを実行すると,ctrl-cで中断するか,数字以外の文字列を入力しない限り無限に続いてしまします.
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
}無限ループを正常に終了させるために,正しい答えを入力した時にbreak文でloopから抜け出すように修正します.
Ordering::Equal => {
println!("You win!");
break;
}また,不正な入力を得た時にエラーを返すのではなく,再び標準入力を求めるようにします.
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};ここまでできれば,プログラムは完成しているはずです.最後に,全貌をのせておきます.
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}まとめ#
ここで出てきた概念
- メソッドについて
- match
- クレートの使い方
- loop