Expresar las reglas con elecciones tipadas

RustBeginner
Practicar Ahora

Introducción

Este juego de cinco dados ofrece tres opciones: sum, pair y straight. Una misma tirada puede dar distintas puntuaciones según tu elección. Para [2, 2, 4, 4, 6], la suma es 18 y la pareja más alta vale 8. Convertirás estas reglas breves en código Rust con tipos definidos y probarás tiradas conocidas y aleatorias.

Debes conocer las funciones, los arrays, enum, match y Option de Rust, además de las rutas básicas de la terminal. Cargo y la interfaz de entrada ya están preparados; tú implementas las decisiones de puntuación. Este laboratorio es independiente de otras VM. Reserva unos 40 minutos para leer y resolver errores habituales; es una estimación del autor.

Modelar una elección y calcular la suma

En este paso, leerás los tipos preparados, predecirás una puntuación y escribirás una suma breve que funcione en la biblioteca real del juego. Empieza en la pestaña Terminal. La preparación ha creado un proyecto Cargo en /home/labex/project/dice-game. src/lib.rs es la biblioteca de reglas; src/main.rs proporciona la entrada y los dados aleatorios. Conserva src/main.rs y Cargo.toml sin cambios.

La cadena de herramientas está en /home/labex/.cargo/bin. export añade ese directorio a PATH, la lista de ubicaciones donde el shell busca comandos. El ajuste dura esta sesión de Terminal; repítelo en un shell nuevo. cd selecciona el proyecto y pwd confirma tu ubicación.

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

Debe aparecer /home/labex/project/dice-game. Corrige una ruta mal escrita con el cd mostrado; no restablezcas el entorno. Antes de editar, predice la suma de [2, 2, 4, 4, 6]. Es 18. Primero harás funcionar ese comando normal de puntuación.

Lee la biblioteca existente con cat, que imprime un archivo sin modificarlo:

cat src/lib.rs

Dice es un alias de [u8; 5]: exactamente cinco valores sin signo de un byte. La interfaz solo admite valores del 1 al 6. Category nombra tres opciones. Category::parse devuelve Some(category) para una palabra reconocida y None para una desconocida. Predice qué devuelve para sum y sums y busca sus ramas de match. La interfaz se encarga de rechazar entradas; una categoría reconocida puede puntuar cero legítimamente.

Los tipos y métodos auxiliares están suministrados. pub expone los nombres a la interfaz. impl Category agrupa métodos en ese tipo; Self significa Category dentro de él. derive proporciona copia, visualización de depuración e igualdad para este enum. name devuelve un literal de cadena fijo para mostrarlo. Conserva estas definiciones. Tu edición se limita a score, cuyo cuerpo actual devuelve cero para todas las categorías.

Conserva un punto de deshacer antes de editar. mktemp -d crea un directorio nuevo, sustituyendo las seis X por un sufijo único en vez de reutilizar una copia anterior. $() captura la ruta impresa en la variable de sesión rs02_before_sum. && ejecuta la copia solo si la creación del directorio tuvo éxito. cp -p copia el código fuente y conserva sus metadatos; las comillas mantienen junta la ruta de la variable. Esta copia contiene únicamente tu biblioteca editable, no la interfaz de entrada preparada ni la configuración de Cargo. Si alguna operación falla, lee el error y consigue una copia correcta antes de editar.

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

Abre ese archivo existente con nano. -c muestra la posición del cursor, útil para seguir los números de línea del compilador:

nano -c src/lib.rs

La notación ^ de nano significa la tecla Ctrl. Pulsa Ctrl+W, escribe pub fn score y pulsa Enter para encontrar la función. Utiliza las flechas para moverte y Backspace/Delete para cambiar caracteres. Lee desde el { inicial de la función hasta su } correspondiente antes de editar. No borres el enum anterior ni los métodos auxiliares.

El código inicial nombra los parámetros sin usar _dice y _category. Cambia solo esos nombres a dice y category para usarlos en el cuerpo; conserva el nombre de la función y los tipos de parámetros y retorno. Sustituye su única expresión 0 por una expresión match category. Una rama tiene un patrón, => y un resultado, por ejemplo la rama temporal Category::Pair => 0,. Dale a Category::Straight el mismo resultado cero temporal. Implementarás esas dos reglas después.

Para Category::Sum, usa un bloque que inicie un acumulador mutable, visite cada valor y sume su valor convertido a un tipo más amplio. Este bucle breve muestra la operación numérica:

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

u16::from(face) amplía el número antes de sumarlo. Incluso cinco seises caben en u16. Después del bucle, haz que total sea la expresión final de la rama Sum: sin punto y coma tras esa expresión final, para que la rama devuelva el valor. Cierra las llaves del match y de la función. Has implementado una categoría real dentro del programa existente, dejando deliberadamente incompletas las otras dos opciones.

Pulsa Ctrl+O para escribir, Enter para confirmar src/lib.rs y Ctrl+X para salir de nano. No escribas comandos de terminal mientras el editor siga abierto. Lee lo guardado antes de compilar:

cat src/lib.rs

Cargo compila y ejecuta el programa preparado. --quiet oculta los mensajes habituales de compilación; -- separa las opciones de Cargo de los argumentos del programa. Ejecuta los mismos cinco valores que predijiste:

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

Los dados no cambiaron; la regla elegida produjo los puntos. Comprueba también que tu bucle prediga 30 para cinco seises antes de continuar.

Si Cargo informa de un error de sintaxis, lee el archivo y el número de línea, reabre nano -c src/lib.rs, busca pub fn score y corrige la llave, rama o expresión correspondiente. Guarda, sal y repite el comando fallido. Mientras una edición siga sin guardar, Ctrl+X y después N a la pregunta de guardado descarta esa edición; no deshace cambios ya escritos.

Para un error ya guardado que no puedas corregir, restaura tu propia biblioteca anterior a la edición con el comando siguiente. Sustituye intencionadamente solo el archivo permitido src/lib.rs; no restaura la interfaz ni los archivos de Cargo. La variable de copia pertenece a esta sesión de Terminal. En otro shell, usa las instrucciones condicionales siguientes para seleccionar e inspeccionar tu copia anterior real.

Solo al recuperar un error ya guardado: en la Terminal original, la variable del punto de control todavía contiene su ruta. En una Terminal nueva, repite primero los comandos anteriores de PATH y del directorio del proyecto. Enumera las copias de esta fase:

ls -d .edit-before-sum.*

ls -d enumera nombres de directorios en vez de su contenido; * coincide con los sufijos de los directorios únicos de esta fase. Son nombres generados reales, así que no escribas un sufijo inventado. Si no coincide ninguna copia, confirma el directorio del proyecto y si creaste este punto de control; no copies desde una fase ajena. Para seleccionar una de las rutas mostradas, ejecuta:

read rs02_before_sum

El shell espera una línea. Pega el nombre exacto del directorio seleccionado y pulsa Enter. read guarda esa línea en la variable indicada para esta sesión. Confirma el contenido de la copia antes de restaurar:

cat "$rs02_before_sum/lib.rs"

Debe contener el código inicial anterior a Sum. Si la ruta o la fase no son las correctas, repite read con la ruta mostrada correcta e inspecciónala otra vez. Solo entonces usa el comando de restauración. Esta comprobación también se aplica a la Terminal original si no sabes con certeza a qué punto de control se refiere su variable.

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

Eso restaura el código inicial, así que repite la implementación de Sum y su comando de puntuación. Conserva esta sesión de Terminal y tu Sum funcional para el paso siguiente.

Puntuar la pareja más alta y una escalera

En este paso, diseñarás e implementarás las decisiones de puntuación restantes conservando tu rama Sum funcional. Continúa en la misma Terminal, dentro de /home/labex/project/dice-game. En un shell nuevo, repite los comandos anteriores export PATH y cd.

Una pareja vale el doble del valor más alto que aparece al menos dos veces. Tres, cuatro o cinco valores iguales siguen aportando solo dos dados. Con [2, 2, 4, 4, 6], ¿deben contribuir ambas parejas o solo los cuatros? Solo los cuatros: 8 puntos. Con cinco seises, el resultado es 12, no 30. Sin pareja, cero es un resultado válido.

Una representación útil es [0u8; 7]: siete contadores indexados del 0 al 6, sin usar el 0. Para cada dado, usize::from(face) convierte su valor en un índice de array; counts[usize::from(face)] += 1 incrementa el contador de ese valor. Un segundo bucle puede inspeccionar los valores de 1..=6, que incluye ambos extremos. Cuando un contador sea al menos dos, sustituye una puntuación máxima mutable por el doble de ese valor; recorrerlos en orden ascendente deja la pareja válida más alta. as u16 convierte ese pequeño resultado numérico al tipo de la puntuación. Puedes organizar otro algoritmo equivalente de recuento u ordenación; debe seguir tratando correctamente dos parejas disponibles y de tres a cinco dados iguales.

Una escalera vale 15 solo con exactamente los valores distintos del 1 al 5 o del 2 al 6, en cualquier orden. Predice la diferencia entre [6, 5, 4, 3, 2] y [1, 2, 3, 4, 6]. La primera es una escalera; la segunda tiene un hueco. Copiar con let mut sorted = dice y llamar a sorted.sort() permite comparar el array ordenado con los dos arrays exactos sin cambiar la tirada original. || significa que basta con que una comparación sea verdadera. Decide cuándo devolver 15 y cuándo cero; los duplicados no deben pasar.

Conserva la fase ya funcional antes de editar. Como antes, el directorio único impide sobrescribir una copia anterior y la copia solo se ejecuta si la creación del directorio tiene éxito:

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

Abre el archivo existente, busca pub fn score con Ctrl+W y cambia solo las ramas Pair y Straight. Conserva Sum, la API pública y todas las definiciones auxiliares. Escribe tu implementación a partir de estas reglas en vez de sustituir el módulo completo.

nano -c src/lib.rs

Guarda con Ctrl+O, Enter y sal con Ctrl+X. Antes de ejecutar, lee las ramas guardadas y predice cada resultado:

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

Compara ahora cinco dados iguales, ambos rangos de escalera y ceros válidos:

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

Deben dar 12, 15, 15, 0 y 0 respectivamente. Cero significa que la regla reconocida no encontró una combinación que puntuara, no que la entrada se rechazó. Elige otro ejemplo válido propio de cinco dados, predice su puntuación para una categoría y ejecuta score con esa categoría y los cinco valores. Explica cualquier desacuerdo usando la regla, en vez de cambiar el ejemplo para ocultarlo.

Omitir los cinco valores utiliza la fuente aleatoria del sistema operativo. Es un juego local, no un sistema de seguridad ni de apuestas. Ejecútalo una vez; puedes repetirlo:

cargo run --quiet -- score pair

Tus dados y puntuación pueden variar y una tirada aleatoria puede repetirse por casualidad. Aplica por tu cuenta la regla de la pareja más alta a los dados mostrados. Una palabra desconocida es distinta de un cero válido: esta errata deliberada termina con estado 1 y muestra Error: choose sum, pair, or straight sin puntuar una tirada:

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

Corrige la categoría en la línea de comandos:

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

Debe aparecer pair: 8. Un error del compilador apunta, en cambio, al código fuente: reabre nano en score, corrige la línea indicada, guarda y repite. Si es necesario, restaura tu propia fase Sum funcional y vuelve a implementar las dos ramas restantes:

Solo al recuperar un error ya guardado: en la Terminal original, la variable del punto de control todavía contiene su ruta. En una Terminal nueva, repite primero los comandos anteriores de PATH y del directorio del proyecto. Enumera las copias de esta fase:

ls -d .edit-before-rules.*

ls -d enumera nombres de directorios en vez de su contenido; * coincide con los sufijos de los directorios únicos de esta fase. Son nombres generados reales, así que no escribas un sufijo inventado. Si no coincide ninguna copia, confirma el directorio del proyecto y si creaste este punto de control; no copies desde una fase ajena. Para seleccionar una de las rutas mostradas, ejecuta:

read rs02_before_rules

El shell espera una línea. Pega el nombre exacto del directorio seleccionado y pulsa Enter. read guarda esa línea en la variable indicada para esta sesión. Confirma el contenido de la copia antes de restaurar:

cat "$rs02_before_rules/lib.rs"

Debe contener Sum funcional antes de Pair y Straight. Si la ruta o la fase no son las correctas, repite read con la ruta mostrada correcta e inspecciónala otra vez. Solo entonces usa el comando de restauración. Esta comprobación también se aplica a la Terminal original si no sabes con certeza a qué punto de control se refiere su variable.

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

Esto restaura deliberadamente solo tu biblioteca editable. No restablezcas el entorno ni sobrescribas src/main.rs o Cargo.toml. La referencia siguiente está disponible después de tu intento para comparar o recuperar una función concreta; no es la secuencia de edición predeterminada.

Referencia completa de la biblioteca para comparación y recuperación

Compara la rama pertinente y transfiere solo la reparación necesaria mediante nano. Vuelve a ejecutar después los ejemplos conocidos y de límite. La biblioteca completa siguiente también es la única fuente mantenida para preparar las reglas ya completas del siguiente laboratorio.

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

Resumen

Has usado una categoría con tipo definido, match, una suma acumulada, contadores de valores y una copia ordenada para expresar tres reglas completas. Los ejemplos fijos explican los resultados y las tiradas aleatorias permiten aplicar las mismas reglas a nuevos dados. Si no hay una combinación que puntúe, el resultado es cero; una categoría desconocida es un error de entrada.