1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rustで作る数当てゲーム

Posted at

はじめに

今回は、Rustで数当てゲームを作成してみた。という記事になります。
ちなみに数当てゲームとは、1から100までの乱数整数を生成し、プレーヤーに予想値を入力させます。
その後、その予想が小さいか大きいかを出力します。予想が当たっていれば、勝ちです。

新規プロジェクトの作成

  • Cargoを使って新規プロジェクトを作成します。
❯ cargo new guessing-game --bin
     Created binary (application) `guessing-game` package

実装

入力部分

  • 数当てゲームの最初の部分は、ユーザに入力を求めその入力を処理し、求めた形式になっていることを確認します。
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);
}

解説

  • ユーザ入力を受け付け、結果を出力するためには、ioライブラリをスコープに導入する必要があります。
  • ioライブラリは、標準ライブラリ(std)に存在します。
use std::io;
  • fn構文が関数を新しく宣言し、( ) は引数がないことを示します。
fn main() {
  • printlnは、文字列を画面に表示するマクロです。
println!("Guess the number!");
println!("Please input your guess.");
  • ユーザが入力する値を保持する場所は、下記のように宣言します。
  • また、Rustでは、変数は標準で不変(immutable)です。
  • そのため、 変数名の前にmutをつけて変数を可変にします。
  • = の右側には、変数guessが束縛される値があります。
  • この値は、String::new関数の呼び出し結果であり、この関数は、String型のオブジェクトを返します。
  • String型は、標準ライブラリによって提供される文字列型で、サイズ可変UTF-8エンコードされたテキストになります。
  • ::newにある、::という記法は、newがString型の関連関数であることを表しています。
  • 関連関数とは、String型の特定のオブジェクトよりも型に対して実装された関数のことです。
  • new関数は、新しく空の文字列を生成します。

=> なので、**let mut guess = String::new();**は、新たに空のStringオブジェクトに束縛されている可変変数を作っている。

let mut guess = String::new();
  • stdin関数は、 std:: io::Stdinオブジェクトを返し、この型はターミナルの標準入力へのハンドルを表す型になります。
  • .read_line(&mut guess)は、標準入力ハンドルのread_lineメソッドを呼び出して、ユーザから入力を受け付けます。

=> read_lineメソッドは、ユーザが標準入力したものすべてを取り出し、文字列に格納することなので、格納する文字列を引数として取ります。

  • この文字列引数は、可変である必要がある。

  • つまりこれにより、ユーザ入力を追記して、文字列の中身を変えられるようになります。

  • **&**は、この引数が参照であることを表します。

  • これのおかげで、データを複数回メモリにコピーせずに複数箇所で同じデータにアクセスできるようになります。

io::stdin().read_line(&mut guess)
    .expect("Failed to read line");
  • read_lineメソッドは、渡された文字列にユーザが入力したものを入れ込むだけでなく、値を返却します。

=> 今回はio::Resultです。

  • Result型は、列挙型(enum)です。

  • Result型では、列挙子はOkErrです。

  • Ok列挙子は、処理が成功したことを表し、中に生成された値を保持します。

  • Err列挙子は、処理が失敗したことを意味し、Errは、処理が失敗した過程や、理由などの情報を保有します。

  • io::Resultオブジェクトには、呼び出し可能なexpectメソッドがあります。

  • このio::ResultオブジェクトErrの場合、expectメソッドは引数として渡されたメッセージを表示します。

.expect("Failed to read line");
  • 値を出力するには、下記のような書き方をします。
  • 下記は、入力を保存した文字列の中身を出力します。{ }は、プレースホルダーの役目を果たします。
println!("You guessed: {}", guess);

入力部分についてテストしてみる

❯ cargo run
   Compiling guessing-game v0.1.0 (/Users/yoshitaka.koitabashi/Desktop/guessing-game)
    Finished dev [unoptimized + debuginfo] target(s) in 5.44s
     Running `target/debug/guessing-game`
Guess the number!
Please input your guess.
6
You guessed: 6

数字の生成

  • ユーザが数当てに挑戦する数字の生成をする必要があります。
  • 今回は、1から100までの乱数を使用します。
  • ちなみに、Rustの標準ライブラリには、乱数機能はまだ含まれていません。

クレートを使用して機能を追加

  • クレートはRustコードのパッケージです。
  • このプロジェクトもバイナリクレートであり、これは実行可能形式になります。

外部クレートを使用するには、下記のようにします。

ファイル名: Cargo.toml

[dependencies]

rand = "0.3.14"

ビルドしてみる

❯ cargo build
    Updating crates.io index
  Downloaded rand v0.3.23
  Downloaded rand v0.4.6
  Downloaded libc v0.2.88
  Downloaded 3 crates (601.9 KB) in 1.55s
   Compiling libc v0.2.88
   Compiling rand v0.4.6
   Compiling rand v0.3.23
   Compiling guessing-game v0.1.0 (/Users/yoshitaka.koitabashi/Desktop/guessing-game)
    Finished dev [unoptimized + debuginfo] target(s) in 29.23s

乱数の生成するコードを実装する

  • use rand::Rngでは、乱数生成器が実装するメソッドを定義しています。
  • rand::thread_rng関数は、特定の乱数生成器を返します。
  • gen_rangeメソッドは二つの数字を引数に取り、それらの間の乱数を生成してくれます。
  • 範囲は下限値を含み、上限値を含みません。
use std::io;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1, 101);

    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 std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1, 101);

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

    let guess: u32 = guess.trim().parse()
        .expect("Please type a number!"); 

    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 型を標準ライブラリからスコープに導入しています。
  • Resultと同じくOrderingもenumです。
  • ただ、Orderingの列挙子は、LessGreaterEqualです。
  • match式を使用して、guess変数とsecret_numberをcmpに渡して返ってきたOrderingの列挙子に基づき、動作します。
  • 下記のmatch式では、ユーザが50を入力し、ランダム生成された数字は38だったとします。
  • コードが50と38を比較すると、 cmpメソッドはOrdering::Greaterを返します。
    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
  • 下記を見るとguessという変数を生成しています。
  • しかし、guessはその前の行で一度変数宣言を行っています。実は、Rustでは新しい値でguessの値を覆い隠すことが許されています。
  • この機能は、値を別の型に変換したいシチュエーションでよく使われます。
  • **シャドーイング(shadowing)**のおかげで別々の変数を2つ作らされることなく、guessという変数名を再利用することができます。
  • 次に、guessを**guess.trim().parse()**という式に束縛しています。
  • Stringオブジェクトのtrimメソッドは、両端の空白をすべて除去します。
  • u32型は、数字しか含むことができませんが、ユーザは、read_lineの処理を終えるためにエンターを押します。=> ユーザがエンターを押したら、改行文字が文字列に追加されます。
  • trimメソッドは、\nを削除するので、ただの数値になります
let guess: u32 = guess.trim().parse()
    .expect("Please type a number!");

細かい調整を行う(ループ,バリデーション,ゲーム終了方法)

  • loopを利用し、ユーザが何回も予想できるようにする。
  • loopキーワードは、無限ループを作り出します。
  • break文を追加して、ユーザが勝った時にゲームを終了させます。
  • ユーザが数値以外を入力した時にプログラムをクラッシュさせるのではなく、非数値を無視してユーザが数当てを続けられるようにします。

[完成したコード]

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1, 101);

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

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

成果物を実行してみる

❯ cargo run
   Compiling guessing-game v0.1.0 (/Users/yoshitaka.koitabashi/Desktop/guessing-game)
    Finished dev [unoptimized + debuginfo] target(s) in 0.28s
     Running `target/debug/guessing-game`
Guess the number!
The secret number is: 45
Please input your guess.
2
You guessed: 2
Too small!
Please input your guess.
4
You guessed: 4
Too small!
Please input your guess.
45
You guessed: 45
You win!

まとめ

  • 今回は、Rustで数当てゲームを作成してみました。
  • いい感じにできたのではと思います!

参考文献

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?