Express the Rules as Typed Choices

RustBeginner
Practice Now

Introduction

A five-dice game gives you three choices: sum, pair, and straight. The same roll can earn different points depending on your choice. For [2, 2, 4, 4, 6], the sum is 18, while the highest pair earns 8. You will turn these short rules into typed Rust code and try both known and random rolls.

You should know Rust functions, arrays, enum, match, and Option, plus basic terminal paths. Cargo and the input shell are prepared; you implement the scoring decisions. This lab is independent of other VMs. Allow about 40 minutes including reading and ordinary mistakes; this is an author estimate.

Model a Choice and Score the Sum

In this step, read the prepared types, predict a score, and write a short working sum in the real game library. Start in the Terminal tab. Setup has prepared /home/labex/project/dice-game with a Cargo project. src/lib.rs is the rule library; src/main.rs supplies input and random dice. Keep src/main.rs and Cargo.toml unchanged.

The toolchain lives in /home/labex/.cargo/bin. export adds that directory to PATH, the list your shell searches for commands. This setting lasts for this Terminal session; repeat it in a new shell. cd selects the project, and pwd confirms your location.

export PATH=/home/labex/.cargo/bin:$PATH
cd /home/labex/project/dice-game
pwd

Expect /home/labex/project/dice-game. Correct a path typo with the shown cd; do not reset the environment. Before editing, predict the sum of [2, 2, 4, 4, 6]. It is 18. You will make that ordinary scoring command work first.

Read the existing library with cat, which prints a file without changing it:

cat src/lib.rs

Dice aliases [u8; 5], exactly five one-byte unsigned values. The shell admits only faces 1 through 6. Category names three choices. Category::parse returns Some(category) for a recognized word and None for an unknown word. Predict what it returns for sum and sums, then find their match branches. Input rejection belongs to the shell; a recognized category can legitimately score zero.

The types and helpers are supplied. pub makes names available to the shell. impl Category groups methods on that type; Self means Category inside it. derive supplies copying, debug display and equality for this enum. name returns a fixed string literal for display. Preserve these definitions. Your edit is confined to score, whose current body returns zero for every category.

Keep an undo point before this edit. mktemp -d creates a new directory, replacing the six X characters with a unique suffix instead of reusing an older backup. $() captures its printed path in the session variable rs02_before_sum. && runs the copy only if directory creation succeeded. cp -p copies the source and preserves its metadata; quotes keep the variable's path together. This backup contains only your editable library, not the prepared input shell or Cargo configuration. If either operation fails, read the error and make a successful backup before editing.

rs02_before_sum=$(mktemp -d .edit-before-sum.XXXXXX) && cp -p src/lib.rs "$rs02_before_sum/lib.rs"

Open that existing file with nano. -c displays the cursor position, useful when following compiler line numbers:

nano -c src/lib.rs

Nano's ^ notation means the Ctrl key. Press Ctrl+W, type pub fn score, and press Enter to find the function. Use arrow keys to move, and Backspace/Delete to change characters. Read from the function's opening { to its matching } before editing. Do not delete the earlier enum or helper methods.

The starter names unused parameters _dice and _category. Rename just those parameter bindings to dice and category so your body can use them; keep the function name and parameter/return types. Replace its single 0 expression with a match category expression. A branch has a pattern, =>, and a result, for example the temporary branch Category::Pair => 0,. Give Category::Straight the same temporary zero result. You will implement those two rules next.

For Category::Sum, use a block that starts a mutable accumulator, visits every face, and adds the widened value. This short loop shows the numeric operation:

let mut total = 0;
for face in dice {
    total += u16::from(face);
}

u16::from(face) widens the number before addition. Even five sixes fit in u16. After this loop, make total the final expression in the Sum branch: no semicolon after that final expression, so the branch returns the value. Close both the match and function braces. You have implemented a real category in the existing program, with the two remaining choices deliberately unfinished.

Press Ctrl+O to write, Enter to confirm src/lib.rs, then Ctrl+X to exit nano. Do not type terminal commands while the editor is still open. Read your saved edit before compiling:

cat src/lib.rs

Cargo compiles and runs the prepared program. --quiet hides routine build messages; -- separates Cargo options from program arguments. Run the same five faces you predicted:

cargo run --quiet -- score sum 2 2 4 4 6
Dice: [2, 2, 4, 4, 6]
sum: 18

The dice did not change; your chosen rule produced the points. Check that your loop also predicts 30 for five sixes before continuing.

If Cargo reports a syntax error, read its filename and line number, reopen nano -c src/lib.rs, search for pub fn score, and repair the corresponding brace, branch or expression. Save and exit, then rerun the failed command. While an edit is still unsaved, Ctrl+X, then N at the save question discards that unsaved edit; it does not undo changes already written.

For an already-saved mistake you cannot repair, restore your own pre-edit library with the command below. It intentionally replaces only the allowed src/lib.rs; it does not restore the shell or Cargo files. The backup variable belongs to this Terminal session. In another shell, use the conditional recovery instructions below to select and inspect your actual earlier backup.

Only when recovering a saved mistake: in the original Terminal, the checkpoint variable still holds its path. In a new Terminal, first repeat the earlier PATH and project-directory commands. Enumerate this stage's backups:

ls -d .edit-before-sum.*

ls -d lists directory names rather than their contents; * matches the suffixes of this phase's unique directories. These are real generated names, so do not type a made-up suffix. If no backup matches, confirm the project directory and whether you made this checkpoint; do not copy from an unrelated phase. To select one of the printed paths, run:

read rs02_before_sum

The shell waits for one line. Paste the exact directory name you selected and press Enter. read stores that line in the named variable for this session. Confirm the backup content before restoring:

cat "$rs02_before_sum/lib.rs"

It should contain the starter before Sum. If the path or stage is wrong, repeat read with the right printed path and inspect it again. Only then use the restore command. This check applies to the original Terminal too if you are unsure which checkpoint its variable names.

cp -p "$rs02_before_sum/lib.rs" src/lib.rs

That restores the starter, so repeat the Sum implementation and its scoring command. Keep this Terminal session and your working Sum for the next step.

Score the Highest Pair and a Straight

In this step, design and implement the remaining scoring decisions while retaining your working Sum branch. Continue in the same Terminal, in /home/labex/project/dice-game. In a new shell, repeat the previous export PATH and cd commands.

A pair earns twice the highest face appearing at least twice. Three, four or five matching faces still contribute only two dice. With [2, 2, 4, 4, 6], should both pairs contribute, or only the fours? Only the fours: 8 points. With five sixes the result is 12, not 30. No pair is a valid zero.

One useful representation is [0u8; 7]: seven counters indexed 0 through 6, with 0 unused. For each die, usize::from(face) converts its value into an array index; counts[usize::from(face)] += 1 increments that face's count. A second loop can inspect faces in 1..=6, which includes both endpoints. When a count is at least two, replace a mutable best score with twice that face; visiting in increasing order leaves the highest eligible pair. as u16 converts that small numeric result to the score type. You may instead organize an equivalent counting or sorting algorithm; it must still handle two available pairs and three through five matching dice correctly.

A straight earns 15 only for exactly the distinct faces 1–5 or 2–6, in any order. Predict the difference between [6, 5, 4, 3, 2] and [1, 2, 3, 4, 6]. The first is a straight; the second has a gap. Copying with let mut sorted = dice and calling sorted.sort() lets you compare the sorted array with the two exact arrays without changing the original roll. || means either comparison may be true. Decide when to return 15 and when to return zero; duplicates must not pass.

Preserve the already-working stage before editing. As before, the unique directory prevents overwriting an earlier backup, and the copy runs only after directory creation succeeds:

rs02_before_rules=$(mktemp -d .edit-before-rules.XXXXXX) && cp -p src/lib.rs "$rs02_before_rules/lib.rs"

Open the existing file, search for pub fn score with Ctrl+W, and change only the Pair and Straight branches. Preserve Sum, the public API and all helper definitions. Write your implementation from these rules rather than replacing the whole module.

nano -c src/lib.rs

Save with Ctrl+O, Enter, and exit with Ctrl+X. Before running, read your saved branches and predict each result:

cat src/lib.rs
cargo run --quiet -- score pair 2 2 4 4 6
Dice: [2, 2, 4, 4, 6]
pair: 8

Now compare five matching dice, both straight ranges, and valid zeros:

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

Expect 12, 15, 15, 0 and 0 respectively. Zero means the recognized rule found no scoring combination, not that the input was rejected. Choose one additional valid five-dice example of your own, predict its score for a category, and run score with that category and all five faces. Explain any disagreement using the rule rather than changing the example to hide it.

Omitting the five faces uses the operating system's random source. This is a local game, not a security or gambling system. Run it once; you may repeat it:

cargo run --quiet -- score pair

Your dice and score may vary, and a random draw can repeat by chance. Independently apply the highest-pair rule to the shown dice. An unknown word is different from a valid zero: this deliberate spelling error exits with status 1 and prints Error: choose sum, pair, or straight without scoring a roll:

cargo run --quiet -- score pairs 2 2 4 4 6

Correct the category at the command line:

cargo run --quiet -- score pair 2 2 4 4 6

Expect pair: 8. A compiler error instead points to your source: reopen nano at score, repair the indicated line, save and retry. If needed, restore your own working Sum stage, then reimplement the two remaining branches:

Only when recovering a saved mistake: in the original Terminal, the checkpoint variable still holds its path. In a new Terminal, first repeat the earlier PATH and project-directory commands. Enumerate this stage's backups:

ls -d .edit-before-rules.*

ls -d lists directory names rather than their contents; * matches the suffixes of this phase's unique directories. These are real generated names, so do not type a made-up suffix. If no backup matches, confirm the project directory and whether you made this checkpoint; do not copy from an unrelated phase. To select one of the printed paths, run:

read rs02_before_rules

The shell waits for one line. Paste the exact directory name you selected and press Enter. read stores that line in the named variable for this session. Confirm the backup content before restoring:

cat "$rs02_before_rules/lib.rs"

It should contain the working Sum before Pair and Straight. If the path or stage is wrong, repeat read with the right printed path and inspect it again. Only then use the restore command. This check applies to the original Terminal too if you are unsure which checkpoint its variable names.

cp -p "$rs02_before_rules/lib.rs" src/lib.rs

This deliberately restores only your editable library. Do not reset the environment or overwrite src/main.rs or Cargo.toml. The following reference is available after your attempt when comparing or recovering a specific function; it is not the default edit sequence.

Complete library reference for comparison and recovery

Compare the relevant branch and transfer only the repair you need through nano. Re-run the known and boundary examples afterward. The complete library below is also the single maintained source used to prepare the next lab's already-complete scoring rules.

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

Summary

You used a typed category, match, a running sum, face counters, and a sorted copy to express three complete rules. Fixed examples explain the results, while random rolls let you use the same rules on new dice. A missing combination scores zero; an unknown category is an input error.