はじめに
5個のサイコロを使うこのゲームには、sum、pair、straight の3つの選択肢があります。同じ出目でも、選んだカテゴリによって得点が変わります。たとえば [2, 2, 4, 4, 6] では、合計は18、最も高いペアの得点は8です。この短いルールを型のある Rust コードで表し、既知の出目とランダムな出目の両方で試します。
Rust の関数、配列、enum、match、Option と、基本的なターミナルのパス操作を理解していることを前提とします。Cargo と入力用プログラムは用意されており、得点を決める処理を自分で実装します。この Lab はほかの VM に依存しません。読解と通常の入力ミスへの対応を含め、所要時間は約40分を見込んでいます。これは著者による見積もりです。
選択肢をモデル化して合計を求める
このステップでは準備済みの型を読み、得点を予想し、実際のゲームライブラリに短い合計処理を書いて動かします。Terminal タブから始めます。準備処理で /home/labex/project/dice-game に Cargo プロジェクトが用意されています。src/lib.rs はルールのライブラリ、src/main.rs は入力とランダムな出目を提供します。src/main.rs と Cargo.toml は変更しません。
ツールチェーンは /home/labex/.cargo/bin にあります。export はこのディレクトリを、シェルがコマンドを探す場所の一覧 PATH に追加します。この設定はこの Terminal セッション中だけ有効で、新しいシェルでは繰り返します。cd でプロジェクトを選び、pwd で現在地を確認します。
export PATH=/home/labex/.cargo/bin:$PATH
cd /home/labex/project/dice-game
pwd
/home/labex/project/dice-game と表示されるはずです。パスの誤記は示した cd で直し、環境をリセットしないでください。編集前に [2, 2, 4, 4, 6] の合計を予想します。18です。まず通常の採点コマンドを動くようにします。
ファイルを変更せず表示する cat で、既存のライブラリを読みます。
cat src/lib.rs
Dice は [u8; 5] の別名で、1バイトの符号なしの値がちょうど5個あることを表します。入力用プログラムは1から6の出目だけを受け付けます。Category は3つの選択肢を表し、Category::parse は既知の単語に Some(category)、未知の単語に None を返します。sum と sums に何を返すか予想し、対応する match 分岐を探してください。入力の拒否は入力用プログラムが担当し、既知のカテゴリでも正当に0点になる場合があります。
型とヘルパーは提供済みです。pub は入力用プログラムから名前を利用できるようにします。impl Category はその型のメソッドをまとめ、内部の Self は Category を意味します。derive はこの列挙型にコピー、デバッグ表示、等値比較を用意します。name は表示用の固定文字列リテラルを返します。これらの定義は保持します。編集するのは score だけで、現在の本体はどのカテゴリにもゼロを返します。
編集前に取り消し用のチェックポイントを残します。mktemp -d は6個の X を一意の接尾辞に置き換えて新しいディレクトリを作るので、古いバックアップを再利用しません。$() は表示されたパスをセッション変数 rs02_before_sum に取り込みます。&& はディレクトリ作成が成功した場合だけコピーを実行します。cp -p はメタデータを保ってソースをコピーし、引用符は変数のパスをひとまとまりに保ちます。バックアップするのは編集可能なライブラリだけで、準備済み入力プログラムや Cargo 設定は含みません。どちらかが失敗したら、エラーを読み、バックアップを成功させてから編集してください。
rs02_before_sum=$(mktemp -d .edit-before-sum.XXXXXX) && cp -p src/lib.rs "$rs02_before_sum/lib.rs"
nano でその既存ファイルを開きます。-c はカーソル位置を表示し、コンパイラーの行番号を追う際に役立ちます。
nano -c src/lib.rs
Nano の ^ 表記は Ctrl キーを意味します。Ctrl+W を押し、pub fn score と入力して Enter を押すと関数が見つかります。矢印キーで移動し、Backspace/Delete で文字を変更します。編集前に、関数の開始の { から対応する } まで読みます。前にある列挙型やヘルパーメソッドを削除しないでください。
初期版の未使用引数の名前は _dice と _category です。本体で使えるよう、その引数束縛の名前だけを dice と category に変えます。関数名、引数と戻り値の型は維持します。単一の 0 式を match category 式に置き換えます。分岐はパターン、=>、結果からなり、たとえば一時的な分岐は Category::Pair => 0, です。Category::Straight も一時的にゼロを返すようにします。この2つのルールは次に実装します。
Category::Sum では、可変の累積変数を作り、各出目を訪れて型を広げた値を加えるブロックを使います。数値操作を示す短いループは次のとおりです。
let mut total = 0;
for face in dice {
total += u16::from(face);
}
u16::from(face) は加算前に数値の型を広げます。6が5個でも u16 に収まります。ループ後、Sum 分岐の最後の式を total にします。その最後の式にはセミコロンを付けず、分岐から値を返します。match と関数の両方の波括弧を閉じます。既存プログラムに本物のカテゴリを実装し、残り2つは意図的に未完成にしました。
Ctrl+O で書き込み、Enter で src/lib.rs を確定し、Ctrl+X で nano を終了します。エディターが開いている間にターミナルコマンドを入力しないでください。コンパイル前に保存した編集内容を読みます。
cat src/lib.rs
Cargo は準備済みプログラムをコンパイルして実行します。--quiet は通常のビルドメッセージを隠し、-- は Cargo のオプションとプログラム引数を区切ります。予想したものと同じ5個の出目で実行します。
cargo run --quiet -- score sum 2 2 4 4 6
Dice: [2, 2, 4, 4, 6]
sum: 18
出目は変わらず、選んだルールが得点を決めました。続ける前に、6が5個ならこのループでも30になると確認してください。
Cargo が構文エラーを報告したら、ファイル名と行番号を読み、nano -c src/lib.rs を開き直し、pub fn score を探して該当する波括弧、分岐、式を直します。保存・終了して失敗したコマンドを再実行します。未保存の編集中なら、Ctrl+X と保存確認での N がその未保存の編集を破棄します。すでに書き込んだ変更は取り消しません。
直せない保存済みの間違いは、次のコマンドで自分の編集前ライブラリを戻します。意図的に置き換えるのは許可された src/lib.rs だけで、入力プログラムや Cargo のファイルは戻しません。バックアップ変数はこの Terminal セッションのものです。別のシェルでは、以下の条件付き復旧手順で自分の実際の以前のバックアップを選び、確認してください。
保存済みの間違いから復旧するときだけ: 元の Terminal ではチェックポイント変数にパスが残っています。新しい Terminal では先に前述の PATH とプロジェクトディレクトリのコマンドを繰り返します。この段階のバックアップを列挙します。
ls -d .edit-before-sum.*
ls -d は内容ではなくディレクトリ名を表示し、* はこの段階の一意なディレクトリの接尾辞に一致します。実際に生成された名前なので、架空の接尾辞を入力しないでください。一致するバックアップがなければ、プロジェクトの場所と、このチェックポイントを作ったかを確認します。無関係な段階からコピーしてはいけません。表示されたパスを1つ選ぶには、次を実行します。
read rs02_before_sum
シェルは1行の入力を待ちます。選んだ正確なディレクトリ名を貼り付けて Enter を押します。read がその行をこのセッションの指定変数に保存します。復元前にバックアップ内容を確認します。
cat "$rs02_before_sum/lib.rs"
Sum 実装前の初期版が入っているはずです。パスや段階が違えば、正しい表示済みパスで read を繰り返し、再確認します。その後だけ復元コマンドを使います。元の Terminal でも、変数がどのチェックポイントを指すか不確かなら、この確認を行います。
cp -p "$rs02_before_sum/lib.rs" src/lib.rs
初期版に戻るので、Sum の実装と採点コマンドをやり直します。この Terminal セッションと動作する Sum を保ち、次のステップに進みます。
最も高いペアとストレートを採点する
このステップでは、動作する Sum 分岐を保ちながら、残りの採点判断を設計・実装します。同じ Terminal の /home/labex/project/dice-game で続けます。新しいシェルでは、前の export PATH と cd を繰り返します。
ペアの得点は、2回以上現れる最も高い出目の2倍です。同じ出目が3個、4個、5個あっても、数えるのはサイコロ2個だけです。[2, 2, 4, 4, 6] では両方のペアか、4だけか、どちらでしょうか。4だけで8点です。6が5個でも30ではなく12点です。ペアなしも正当な0点です。
便利な表し方の1つが [0u8; 7] です。0から6で参照する7個のカウンタで、0は未使用です。各サイコロで usize::from(face) が値を配列の添字に変換し、counts[usize::from(face)] += 1 がその出目の数を増やします。次のループで両端を含む 1..=6 の出目を調べられます。数が2以上なら、可変の最良得点をその出目の2倍で置き換えます。昇順に調べることで、最後に最も高い該当ペアが残ります。as u16 は小さな数値の結果を得点型へ変換します。同等のカウントやソート方法でも構いませんが、2種類のペアがある場合や同じ出目が3〜5個の場合も正しく扱ってください。
ストレートは、順序を問わず、重複しない出目が正確に1〜5または2〜6の場合だけ15点です。[6, 5, 4, 3, 2] と [1, 2, 3, 4, 6] の違いを予想してください。前者はストレート、後者には欠けがあります。let mut sorted = dice でコピーし、sorted.sort() を呼ぶと、元の出目を変えず、並べ替えた配列を2つの正確な配列と比較できます。|| はどちらかの比較が真ならよいという意味です。15かゼロを返す条件を考えてください。重複は通してはいけません。
編集前に、すでに動作する段階を保管します。先ほどと同様、一意のディレクトリで古いバックアップの上書きを防ぎ、ディレクトリ作成が成功した場合だけコピーします。
rs02_before_rules=$(mktemp -d .edit-before-rules.XXXXXX) && cp -p src/lib.rs "$rs02_before_rules/lib.rs"
既存のファイルを開き、Ctrl+W で pub fn score を検索し、Pair と Straight の分岐だけを変えます。Sum、公開 API、すべてのヘルパー定義は保ちます。モジュール全体を置き換えず、これらのルールから自分で実装を書きます。
nano -c src/lib.rs
Ctrl+O、Enter で保存し、Ctrl+X で終了します。実行前に保存した分岐を読み、各結果を予想します。
cat src/lib.rs
cargo run --quiet -- score pair 2 2 4 4 6
Dice: [2, 2, 4, 4, 6]
pair: 8
同じ出目が5個、両方のストレート範囲、正当な0点を比較します。
cargo run --quiet -- score pair 6 6 6 6 6
cargo run --quiet -- score straight 6 5 4 3 2
cargo run --quiet -- score straight 1 2 3 4 5
cargo run --quiet -- score pair 1 2 3 4 6
cargo run --quiet -- score straight 1 2 3 4 6
順に12、15、15、0、0です。ゼロは既知のルールで得点になる組み合わせがなかったという意味で、入力が拒否されたのではありません。自分でもう1つ有効な5個の出目を選び、カテゴリに対する得点を予想して、そのカテゴリと5個の出目すべてを score に渡して実行します。食い違いがあれば、例を変えて隠すのではなく、ルールで説明してください。
5個の出目を省略すると OS の乱数源を使います。これはローカルゲームであり、セキュリティや賭博のシステムではありません。1回実行し、繰り返しても構いません。
cargo run --quiet -- score pair
出目と得点は変化し、ランダムでも偶然同じ出目になる場合があります。表示された出目に最高ペアのルールを自分で適用してください。未知の単語は正当なゼロと異なります。次の意図的な誤記はステータス1で終了し、出目を採点せず Error: choose sum, pair, or straight を表示します。
cargo run --quiet -- score pairs 2 2 4 4 6
コマンドラインでカテゴリを修正します。
cargo run --quiet -- score pair 2 2 4 4 6
pair: 8 になるはずです。コンパイルエラーならソースの問題です。nano で score を開き直して該当行を直し、保存して再試行します。必要なら自分の動作する Sum の段階に戻し、残り2つの分岐を実装し直します。
保存済みの間違いから復旧するときだけ: 元の Terminal ではチェックポイント変数にパスが残っています。新しい Terminal では先に前述の PATH とプロジェクトディレクトリのコマンドを繰り返します。この段階のバックアップを列挙します。
ls -d .edit-before-rules.*
ls -d は内容ではなくディレクトリ名を表示し、* はこの段階の一意なディレクトリの接尾辞に一致します。実際に生成された名前なので、架空の接尾辞を入力しないでください。一致するバックアップがなければ、プロジェクトの場所と、このチェックポイントを作ったかを確認します。無関係な段階からコピーしてはいけません。表示されたパスを1つ選ぶには、次を実行します。
read rs02_before_rules
シェルは1行の入力を待ちます。選んだ正確なディレクトリ名を貼り付けて Enter を押します。read がその行をこのセッションの指定変数に保存します。復元前にバックアップ内容を確認します。
cat "$rs02_before_rules/lib.rs"
Pair と Straight の前の、動作する Sum が入っているはずです。 パスや段階が違えば、正しい表示済みパスで read を繰り返し、再確認します。その後だけ復元コマンドを使います。元の Terminal でも、変数がどのチェックポイントを指すか不確かなら、この確認を行います。
cp -p "$rs02_before_rules/lib.rs" src/lib.rs
意図的に復元するのは編集可能なライブラリだけです。環境をリセットしたり src/main.rs や Cargo.toml を上書きしたりしないでください。次の参考実装は、自分で試した後で特定の関数を比較・復旧するときに使えます。通常の編集手順ではありません。
比較と復旧のための完全なライブラリ参考実装
該当する分岐を比較し、必要な修正だけを nano で取り込みます。その後、既知の例と境界の例を再実行します。下の完全なライブラリは、次のラボの完成済み採点ルールを準備する、単一の保守対象ソースでもあります。
pub type Dice = [u8; 5];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Category {
Sum,
Pair,
Straight,
}
impl Category {
pub fn parse(input: &str) -> Option<Self> {
match input {
"sum" => Some(Self::Sum),
"pair" => Some(Self::Pair),
"straight" => Some(Self::Straight),
_ => None,
}
}
pub fn name(self) -> &'static str {
match self {
Self::Sum => "sum",
Self::Pair => "pair",
Self::Straight => "straight",
}
}
}
pub fn score(dice: Dice, category: Category) -> u16 {
match category {
Category::Sum => {
let mut total = 0;
for face in dice {
total += u16::from(face);
}
total
}
Category::Pair => {
let mut counts = [0u8; 7];
for face in dice {
counts[usize::from(face)] += 1;
}
let mut best = 0;
for face in 1..=6 {
if counts[face] >= 2 {
best = (face * 2) as u16;
}
}
best
}
Category::Straight => {
let mut sorted = dice;
sorted.sort();
if sorted == [1, 2, 3, 4, 5] || sorted == [2, 3, 4, 5, 6] {
15
} else {
0
}
}
}
}
まとめ
型のあるカテゴリ、match、合計の累積、出目のカウンタ、ソートしたコピーを使い、3つのルールを完成させました。固定例で結果の理由を理解し、ランダムな出目にも同じルールを適用しました。得点になる組み合わせがなければ0点であり、未知のカテゴリは入力エラーです。



