Ведите корректную карточку результатов по раундам

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

Введение

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

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

Подтверждайте только допустимый выбор

На этом шаге вы спроектируете и реализуете переход состояния для одного принятого выбора. Начните во вкладке Terminal. В независимом проекте уже есть полные правила начисления очков. Файлы первой работы не используются. Настройте путь поиска для этой оболочки и войдите в проект; повторите команды в новой оболочке:

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

Начните с обычного рабочего начисления и предскажите его до запуска:

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

Ожидаются показанные кости и pair: 8. Это подтверждает пригодность подготовленного слоя правил до добавления состояния партии. Прочитайте заготовку:

cat src/state.rs

struct объединяет именованные поля одного объекта. Round записывает пять костей, категорию и очки. Game хранит current: Option<Dice> и rounds: Vec<Round>. Some(dice) означает, что ожидается выбор; None — что текущего броска нет. Vec<Round> — расширяемая коллекция; Vec::new() создаёт пустую, а push добавляет элемент.

Прочитайте также вызывающий код подготовленной оболочки:

cat src/main.rs

Найдите match game.submit(input.trim()). Ветвь Err выводит отказ. Ветвь Ok выводит очки, затем получает другой бросок, только если записано меньше трёх раундов. Вы не меняете оболочку. Ваш метод должен оставлять состояние при отказе неизменным, чтобы следующее приглашение показывало те же кости и число бросков.

Предскажите поля после каждого вида подтверждения:

Подтверждение rounds после вызова current после вызова
Нет ожидающих костей, неизвестное слово или использованная категория Без изменений Без изменений
Допустимая неиспользованная категория, включая нулевые очки Добавлена одна полная новая запись None

Следующий бросок после успеха предоставляет оболочка, а не submit. Не бросайте кости в методе. impl Game группирует методы Game. &mut self позволяет методу обновлять партию через изменяемую ссылку. Result<u16, &'static str> означает Ok(points) или Err(message); &'static str — тип фиксированного текста ошибок. В отличие от Option, Result может объяснить ошибку.

ok_or(message) превращает Some в Ok и None в Err. Следующий ? продолжает с успешным значением или немедленно возвращает ошибку. Например, проверка ожидающего броска может выглядеть так:

let dice = self.current.ok_or("match finished")?;

Примените ту же идею к Category::parse(input). До изменения любого поля заимствуйте предыдущие записи циклом for по &self.rounds и отклоните совпадающую категорию. Вы выбираете организацию проверок; таблица — контракт. После проверки получите points через score(dice, category). Успешное подтверждение имеет три действия:

self.rounds.push(Round { dice, category, points });
self.current = None;
Ok(points)

Краткие имена полей используют одноимённые локальные переменные. Ноль тоже успешен. Ни одно из изменений не относится к пути отказа.

Не меняйте src/main.rs, Cargo.toml, публичные типы, имена и типы функций и экспорты модуля. use crate::{score, Category, Dice} импортирует из текущей библиотеки; mod state и pub use в src/lib.rs связывают модули. Здесь меняется только submit в src/state.rs; структуры, метод new и незавершённый replay остаются.

До редактирования сделайте уникальную копию разрешённого файла. Как изучено ранее, mktemp -d заменяет шесть X уникальным суффиксом. $() сохраняет напечатанный путь; && запускает cp -p только после успешного создания каталога. Путь в кавычках остаётся одним аргументом. При ошибке прочитайте её и получите копию до редактирования.

rs02_before_submit=$(mktemp -d .edit-before-submit.XXXXXX) && cp -p src/state.rs "$rs02_before_submit/state.rs"

Откройте файл в nano; -c показывает позицию курсора:

nano -c src/state.rs

Нажмите Ctrl+W, найдите pub fn submit и нажмите Enter. Изучите открывающую и закрывающую скобки метода. Переименуйте неиспользуемую привязку параметра _input в input, не меняя тип. Замените только незавершённое тело своей реализацией проверок и успешного подтверждения выше. Сохраните другие методы и определения данных. Редактируйте стрелками и Backspace/Delete. Сохраните через Ctrl+O, Enter, затем выйдите через Ctrl+X. Обозначение ^ в nano означает Ctrl. Случайную несохранённую правку отменяет Ctrl+X, затем N на вопрос о сохранении; это не отменяет прежнюю сохранённую правку.

Прочитайте сохранённый метод до запуска:

cat src/state.rs

Используйте известную фиксированную последовательность для наблюдения решений о состоянии. --fixture выбирает объявленные броски вместо случайного источника:

cargo run --quiet -- play --fixture

Дождитесь каждого приглашения, введите одно слово и нажмите Enter:

Сначала введите pairs. Слово неизвестно: после отказа ожидаются те же [2, 2, 4, 4, 6] и rolls 1. Затем введите pair. Это даёт 8 и переводит к [1, 2, 3, 4, 6], rolls 2. Снова введите pair. Использованная категория отклоняется, оставляя кости и rolls 2 неизменными. Затем введите straight. Допустимый выбор даёт ноль и переводит к третьему броску. Наконец введите sum. Это даёт 20.

Ожидаемая конечная ошибка — Error: replay not implemented, со статусом 1. Подтверждения сработали, но replay остаётся исходной незавершённой функцией, поэтому оболочка отказывается сохранять непроверенную карточку. Это запланированное промежуточное состояние, а не успешно сохранённая игра. Если случайно выбрали другую допустимую категорию, завершите неиспользованные либо нажмите Ctrl+C и намеренно начните новую фиксированную игру. Внутри партии отказ не должен ничего перезапускать или перебрасывать.

При ошибке компилятора прочитайте файл/строку, снова откройте nano, найдите затронутый метод и исправьте его. Если сохранённую ошибку не удаётся исправить, восстановите свой файл до submit, затем снова реализуйте и проверьте submit:

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

ls -d .edit-before-submit.*

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

read rs02_before_submit

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

cat "$rs02_before_submit/state.rs"

В ней должен быть исходник до submit. Если путь или этап неверен, повторите read с правильным напечатанным путём и снова изучите его. Только затем используйте команду восстановления. Это относится и к исходному Terminal, если вы не уверены, какую точку называет его переменная.

cp -p "$rs02_before_submit/state.rs" src/state.rs

Команда намеренно заменяет только редактируемый файл состояния. Переменная копии локальна для этого Terminal; в другой оболочке используйте условные инструкции восстановления. Не запускайте setup заново и не заменяйте подготовленную оболочку ради ошибки состояния.

Пересчитайте и сохраните полную карточку

На этом шаге вы реализуете независимую проверку каждого записанного раунда до принятия суммы. Продолжайте в том же Terminal, в /home/labex/project/dice-game; при необходимости повторите прежние export PATH и cd. Сохраните завершённый метод submit.

Сначала вручную проверьте предлагаемую фиксированную карточку: pair для [2, 2, 4, 4, 6] даёт 8, straight для [1, 2, 3, 4, 6] — 0, sum для [6, 5, 4, 3, 2] — 20. Пересчитанная сумма равна 28. Если кто-то записал 10 вместо 8 в первой строке, сложение сохранённых очков скрыло бы неверную запись. Карточка не может доказывать себя собственными сохранёнными суммой или очками.

replay получает &[Round], срез: заимствованное представление записей без передачи владения или изменения. Спроектируйте проверки до написания. Требуйте ровно три записи. В каждой отклоните значения вне 1–6 до вызова score, поскольку реализация начисления может использовать значение как индекс массива. Храните использованные категории в небольшом Vec; contains проверяет, встречалась ли текущая категория. Отклоняйте повторы. Пересчитайте очки по реальным костям и категории, сравните с сохранёнными. Добавляйте к сумме только пересчитанный результат и возвращайте сумму только после правильности каждой записи.

Для неудачного условия можно использовать ранний return Err("..."). В for round in rounds каждый раунд заимствован; round.dice — небольшой массив Copy, который можно изучить без изменения записи. Условие !(1..=6).contains(&face) означает значение за пределами включительного диапазона. Выберите осмысленные сообщения отказа; реализация может организовать решения иначе, сохраняя контракт.

Сохраните новую точку отмены с уже рабочим submit. Не путайте её с прежней копией стартового кода:

rs02_before_replay=$(mktemp -d .edit-before-replay.XXXXXX) && cp -p src/state.rs "$rs02_before_replay/state.rs"

Откройте тот же файл и найдите pub fn replay через Ctrl+W:

nano -c src/state.rs

Переименуйте неиспользуемую привязку _rounds в rounds, сохранив тип, и реализуйте только тело этой функции. Оставьте submit, структуры и связи модулей прежними. Используйте упорядоченные проверки и накопитель; не возвращайте просто 28, не доверяйте сохранённым очкам и не пропускайте неверную запись молча. Сохраните через Ctrl+O, Enter, затем выйдите через Ctrl+X. Прочитайте сохранённый код:

cat src/state.rs

Снова запустите фиксированную последовательность:

cargo run --quiet -- play --fixture

Введите pair, затем straight, затем sum, ожидая каждого приглашения. Итоговый вывод включает:

1 | [2, 2, 4, 4, 6] | pair | 8
2 | [1, 2, 3, 4, 6] | straight | 0
3 | [6, 5, 4, 3, 2] | sum | 20
Replayed total: 28
Saved match.tsv

Подготовленная оболочка записывает match.tsv только после прохождения полной карточкой replay. В каждой строке пять значений через пробелы, табуляция, категория, ещё табуляция и очки. Формат обрабатывает оболочка; вы не реализуете файловый парсер. Завершённая новая партия заменяет эту карточку курса, а незавершённая оставляет прежнюю без изменений.

Прочитайте и пересчитайте настоящий сохранённый файл:

cat match.tsv
cargo run --quiet -- replay match.tsv

Ожидаются те же записи и сумма 28, без Saved match.tsv. Replay читает точный путь без изменения или подстановки образца. Чтобы проверить, что вы не зашили прежний порядок категорий в submit, ещё раз сыграйте фиксированную партию:

cargo run --quiet -- play --fixture

Используйте straight, sum, затем pair. Предскажите 0, 16 и 0: все три выбора допустимы, пересчитанная сумма равна 16. Завершённая партия теперь заменяет прежнюю карточку. Пересчитайте текущий файл:

cargo run --quiet -- replay match.tsv

Записи должны отражать вторую последовательность, а не прежнюю партию на 28 очков. Другая сумма ожидаема, поскольку выбранное правило каждого броска изменилось. Следующий шаг использует действительно случайные кости с теми же решениями.

При ошибке компилятора снова откройте указанный файл и исправьте отмеченную строку replay. При необходимости восстановите собственную точку до replay, сохраняющую submit, затем снова реализуйте replay:

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

ls -d .edit-before-replay.*

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

read rs02_before_replay

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

cat "$rs02_before_replay/state.rs"

В ней должен быть рабочий submit до replay. Если путь или этап неверен, повторите read с правильным напечатанным путём и снова изучите его. Только затем используйте команду восстановления. Это относится и к исходному Terminal, если вы не уверены, какую точку называет его переменная.

cp -p "$rs02_before_replay/state.rs" src/state.rs

Восстановление исходника не меняет существующую карточку. Более поздняя завершённая игра заменяет её только после успешного replay; неудачная игра и чтение карточки — нет. Полный эталон ниже служит сравнению конкретной функции после вашей попытки, а не замене модуля как обычному учебному действию.

Полный эталон состояния для сравнения и восстановления

Сохраните поля и предыдущую успешную работу. Сравните только исправляемую функцию, отредактируйте её в nano, затем снова запустите фиксированную партию и пересчитайте настоящую карточку.

use crate::{score, Category, Dice};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Round {
    pub dice: Dice,
    pub category: Category,
    pub points: u16,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Game {
    pub current: Option<Dice>,
    pub rounds: Vec<Round>,
}

impl Game {
    pub fn new(first: Dice) -> Self {
        Self { current: Some(first), rounds: Vec::new() }
    }

    pub fn submit(&mut self, input: &str) -> Result<u16, &'static str> {
        let dice = self.current.ok_or("match finished")?;
        let category = Category::parse(input).ok_or("unknown category")?;
        for round in &self.rounds {
            if round.category == category {
                return Err("category already used");
            }
        }
        let points = score(dice, category);
        self.rounds.push(Round { dice, category, points });
        self.current = None;
        Ok(points)
    }
}

pub fn replay(rounds: &[Round]) -> Result<u16, &'static str> {
    if rounds.len() != 3 {
        return Err("card needs three rounds");
    }
    let mut used = Vec::new();
    let mut total = 0;
    for round in rounds {
        for face in round.dice {
            if !(1..=6).contains(&face) {
                return Err("card has an invalid face");
            }
        }
        if used.contains(&round.category) {
            return Err("card repeats a category");
        }
        let expected = score(round.dice, round.category);
        if expected != round.points {
            return Err("card has incorrect points");
        }
        used.push(round.category);
        total += expected;
    }
    Ok(total)
}

Сыграйте и используйте результат своей партии

На этом шаге вы примените готовые правила к новым костям и исправите обычную ошибку пути к файлу. Продолжайте в том же Terminal, в /home/labex/project/dice-game.

Без --fixture подготовленная оболочка читает случайные байты из /dev/urandom. Каждый бросок содержит пять значений от 1 до 6. Это локальная учебная игра. Не нужно достигать заданной суммы или находить выигрышную стратегию, но очки должны соответствовать правилам. sum, pair и straight нужно использовать по одному разу в любом порядке. Отклонённые слова и повторные категории сохраняют текущий бросок.

Начните новую партию:

cargo run --quiet -- play

Прочитайте первые кости, выберите ещё не использованную категорию и нажмите Enter. Повторите для второго и третьего бросков. Можно намеренно один раз ввести pairs, увидеть отказ и исправить выбор на доступную категорию. Во время исправления наблюдайте неизменные кости и счётчик rolls. После трёх принятых выборов проверьте три записи и пересчитанную сумму. Случайные значения могут отличаться от фиксированной последовательности; завершённая игра заменяет match.tsv.

Прочитайте новую карточку:

cargo run --quiet -- replay match.tsv

Записи должны соответствовать последней партии, включая допустимые нулевые очки. Чтобы потренировать обычную ошибку пути, следующая команда намеренно запрашивает несуществующий файл. Ожидаются сообщение Error: missing-card.tsv: ... и статус 1; заменяющая карточка не должна создаваться:

cargo run --quiet -- replay missing-card.tsv

Исправьте ошибку, используя настоящее имя файла:

cargo run --quiet -- replay match.tsv

Карточка остаётся читаемой и не изменяется ни при одном пересчёте. Сохраните src/main.rs, Cargo.toml и публичный API. Проверка выполняет вашу исходную реализацию в изолированной копии и читает настоящий match.tsv; она не создаёт карточку за вас.

Резюме

Вы смоделировали партию и её записи, явно возвращали успех или отказ и изменяли состояние только после проверки. Недопустимый выбор сохранял бросок, допустимые нулевые очки продвигали партию, а пересчёт проверял полную карточку. Вы сыграли с новыми костями и использовали настоящий сохранённый файл, не полагаясь на предыдущую VM.