Выразите правила через типизированные варианты

RustBeginner
Практиковаться сейчас

Введение

В этой игре с пятью костями есть три варианта: sum, pair и straight. Один и тот же бросок может принести разные очки в зависимости от выбора. Для [2, 2, 4, 4, 6] сумма равна 18, а старшая пара приносит 8 очков. Вы выразите эти короткие правила в типизированном коде Rust и попробуете известные и случайные броски.

Нужно знать функции Rust, массивы, enum, match и Option, а также основы работы с путями в терминале. Cargo и оболочка ввода подготовлены; вы реализуете решения о начислении очков. Эта лабораторная работа не зависит от других VM. Планируйте около 40 минут, включая чтение и исправление обычных ошибок; это оценка автора.

Смоделируйте выбор и вычислите сумму

На этом шаге вы прочитаете подготовленные типы, предскажете очки и напишете короткое рабочее суммирование в настоящей библиотеке игры. Начните во вкладке Terminal. Подготовка создала проект Cargo в /home/labex/project/dice-game. 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 до 6. Category задаёт три выбора. Category::parse возвращает Some(category) для распознанного слова и None для неизвестного. Предскажите результат для sum и sums, затем найдите их ветви match. Отказ ввода относится к оболочке; распознанная категория может законно дать ноль.

Типы и вспомогательные методы предоставлены. pub делает имена доступными оболочке. impl Category группирует методы типа; Self внутри означает Category. derive предоставляет копирование, отладочное отображение и равенство для этого перечисления. name возвращает фиксированный строковый литерал для отображения. Сохраните эти определения. Правка ограничена score, чьё текущее тело возвращает ноль для любой категории.

Сохраните точку отмены до правки. mktemp -d создаёт новый каталог, заменяя шесть символов 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 такой же временный нулевой результат. Эти два правила вы реализуете далее.

Для Category::Sum используйте блок, создающий изменяемый накопитель, посещающий каждое значение и прибавляющий расширенное число. Короткий цикл показывает числовую операцию:

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

u16::from(face) расширяет число до сложения. Даже пять шестёрок помещаются в u16. После цикла сделайте total последним выражением ветви Sum: без точки с запятой после него, чтобы ветвь вернула значение. Закройте скобки match и функции. Вы реализовали настоящую категорию в существующей программе, намеренно оставив два выбора незавершёнными.

Нажмите Ctrl+O, чтобы записать, Enter, чтобы подтвердить src/lib.rs, затем Ctrl+X, чтобы выйти из nano. Не вводите команды терминала, пока редактор открыт. Прочитайте сохранённую правку до компиляции:

cat src/lib.rs

Cargo компилирует и запускает подготовленную программу. --quiet скрывает обычные сообщения сборки; -- отделяет параметры Cargo от аргументов программы. Запустите те же пять значений, которые предсказали:

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

Кости не изменились; выбранное правило дало очки. До продолжения проверьте, что ваш цикл также предсказывает 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 показывает имена каталогов вместо содержимого; * совпадает с суффиксами уникальных каталогов этого этапа. Это настоящие созданные имена, поэтому не вводите выдуманный суффикс. Если совпадений нет, проверьте каталог проекта и создание этой точки; не копируйте из другого этапа. Чтобы выбрать один напечатанный путь, выполните:

read rs02_before_sum

Оболочка ждёт одну строку. Вставьте точное выбранное имя каталога и нажмите 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, 4, 4, 6] учитываются обе пары или только четвёрки? Только четвёрки: 8 очков. Пять шестёрок дают 12, а не 30. Отсутствие пары — допустимый ноль.

Удобное представление — [0u8; 7]: семь счётчиков с индексами 0–6, индекс 0 не используется. Для каждой кости usize::from(face) превращает значение в индекс массива; counts[usize::from(face)] += 1 увеличивает счётчик этого значения. Второй цикл может проверять значения в 1..=6, включая обе границы. Если счётчик не меньше двух, замените изменяемый лучший результат удвоенным значением; обход по возрастанию оставляет старшую подходящую пару. as u16 преобразует небольшой числовой результат в тип очков. Можно вместо этого организовать эквивалентный подсчёт или сортировку; алгоритм должен правильно обрабатывать две доступные пары и от трёх до пяти одинаковых костей.

Стрит даёт 15 только для ровно пяти различных значений 1–5 или 2–6 в любом порядке. Предскажите разницу между [6, 5, 4, 3, 2] и [1, 2, 3, 4, 6]. Первый — стрит, во втором пропуск. Копирование через let mut sorted = dice и вызов sorted.sort() позволяют сравнить отсортированный массив с двумя точными массивами без изменения исходного броска. || означает, что любое сравнение может быть истинным. Решите, когда возвращать 15, а когда ноль; повторы не должны проходить.

Сохраните уже рабочий этап до редактирования. Как прежде, уникальный каталог не перезапишет старую копию, а копирование выполняется только после успешного создания каталога:

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

Откройте существующий файл, найдите pub fn score через Ctrl+W и измените только ветви 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

Теперь сравните пять одинаковых костей, оба диапазона стрита и допустимые нули:

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. Ноль означает, что распознанное правило не нашло комбинацию с очками, а не отказ ввода. Выберите собственный дополнительный допустимый пример из пяти костей, предскажите очки для категории и запустите score с этой категорией и всеми пятью значениями. Объясните расхождение правилом, а не меняйте пример, скрывая его.

Если опустить пять значений, используется источник случайных данных операционной системы. Это локальная игра, а не система безопасности или ставок. Запустите один раз; можно повторить:

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, затем снова реализуйте две оставшиеся ветви:

Только при восстановлении сохранённой ошибки: в исходном Terminal переменная контрольной точки всё ещё хранит путь. В новом Terminal сначала повторите прежние команды PATH и каталога проекта. Перечислите копии этого этапа:

ls -d .edit-before-rules.*

ls -d показывает имена каталогов вместо содержимого; * совпадает с суффиксами уникальных каталогов этого этапа. Это настоящие созданные имена, поэтому не вводите выдуманный суффикс. Если совпадений нет, проверьте каталог проекта и создание этой точки; не копируйте из другого этапа. Чтобы выбрать один напечатанный путь, выполните:

read rs02_before_rules

Оболочка ждёт одну строку. Вставьте точное выбранное имя каталога и нажмите Enter. read сохраняет строку в указанную переменную этой сессии. Проверьте содержимое копии до восстановления:

cat "$rs02_before_rules/lib.rs"

В ней должна быть рабочая Sum до Pair и Straight. Если путь или этап неверен, повторите 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, накопление суммы, счётчики значений и отсортированную копию, чтобы выразить три полных правила. Фиксированные примеры объясняют результаты, а случайные броски позволяют применить те же правила к новым костям. Если подходящей комбинации нет, результат равен нулю; неизвестная категория означает ошибку ввода.