Introducción
Una puntuación es solo una decisión. Una partida completa debe recordar las categorías utilizadas y conservar los mismos dados mientras corriges una elección inválida. Completarás un juego de tres rondas y guardarás una tarjeta que se puede comprobar recalculando las puntuaciones a partir de los dados y las categorías registrados.
Las reglas de puntuación del primer laboratorio ya están instaladas en este proyecto independiente. Debes conocer esas reglas, las funciones de Rust, enum, match, Option, arrays y rutas básicas de la terminal. Este laboratorio presenta las estructuras preparadas, los métodos mutables, Result y los slices cuando los necesitas. Reserva unos 55 minutos para leer y resolver errores habituales; es una estimación del autor.
Confirmar solo una elección legal
En este paso, diseñarás e implementarás la transición de estado de una elección aceptada. Empieza en la pestaña Terminal. Este proyecto independiente ya tiene reglas de puntuación completas. No utiliza archivos del primer laboratorio. Configura la ruta de búsqueda de este shell y entra en el proyecto; repite estos comandos en un shell nuevo:
export PATH=/home/labex/.cargo/bin:$PATH
cd /home/labex/project/dice-game
Empieza con una puntuación normal funcional y predícela antes de ejecutarla:
cargo run --quiet -- score pair 2 2 4 4 6
Deben aparecer los dados indicados y pair: 8. Esto demuestra que la capa de reglas proporcionada es utilizable antes de añadir estado a la partida. Lee el esqueleto:
cat src/state.rs
Un struct agrupa campos con nombre que describen un objeto. Round registra cinco dados, una categoría y puntos. Game contiene current: Option<Dice> y rounds: Vec<Round>. Some(dice) indica una elección pendiente; None indica que no hay tirada actual. Vec<Round> es una colección ampliable; Vec::new() crea una vacía y push añade un elemento.
Lee también el llamador en la interfaz preparada:
cat src/main.rs
Localiza match game.submit(input.trim()). Su rama Err imprime un rechazo. Su rama Ok imprime puntos y después obtiene otra tirada solo si hay menos de tres rondas registradas. No editas esta interfaz. Tu método debe dejar el estado intacto al rechazar, para que la siguiente solicitud muestre los mismos dados y contador de tiradas.
Predice los campos después de cada tipo de envío:
| Envío | rounds después de la llamada |
current después de la llamada |
|---|---|---|
| Sin dados pendientes, palabra desconocida o categoría usada | Sin cambios | Sin cambios |
| Categoría válida sin usar, incluida una puntuación cero | Se añade un registro nuevo completo | None |
La interfaz, no submit, proporciona la siguiente tirada después de un éxito. No generes dados en tu método. impl Game agrupa métodos de Game. &mut self permite que este método actualice esa partida mediante una referencia mutable. Result<u16, &'static str> significa Ok(points) o Err(message); &'static str es el tipo de texto de los mensajes de error fijos. A diferencia de Option, Result puede explicar un fallo.
ok_or(message) convierte Some en Ok y None en Err. Un ? posterior continúa con el valor correcto o devuelve inmediatamente el error. Por ejemplo, la comprobación de tirada pendiente puede ser:
let dice = self.current.ok_or("match finished")?;
Aplica la misma idea a Category::parse(input). Antes de cambiar cualquier campo, toma prestados los registros anteriores con un bucle for sobre &self.rounds y rechaza una categoría coincidente. Tú eliges cómo organizar las comprobaciones; la tabla es el contrato. Tras validar, obtén points de score(dice, category). La confirmación correcta tiene tres acciones:
self.rounds.push(Round { dice, category, points });
self.current = None;
Ok(points)
Los nombres de campo abreviados usan las variables locales del mismo nombre. Un resultado cero sigue siendo correcto. Ninguno de estos cambios pertenece a una ruta de rechazo.
Conserva src/main.rs, Cargo.toml, los tipos públicos, los nombres y tipos de funciones y las exportaciones de módulo sin cambios. use crate::{score, Category, Dice} importa desde la biblioteca actual; mod state y pub use en src/lib.rs conectan los módulos. Este paso edita solo submit en src/state.rs, dejando intactos sus structs, el método new y replay incompleto.
Antes de editar, crea una copia única de este archivo permitido. Como se enseñó, mktemp -d sustituye seis caracteres X por un sufijo único. $() guarda la ruta impresa; && ejecuta cp -p solo después de crear correctamente el directorio. La ruta entre comillas sigue siendo un argumento. Si falla, lee el error y consigue una copia antes de editar.
rs02_before_submit=$(mktemp -d .edit-before-submit.XXXXXX) && cp -p src/state.rs "$rs02_before_submit/state.rs"
Abre el archivo con nano; -c muestra la posición del cursor:
nano -c src/state.rs
Pulsa Ctrl+W, busca pub fn submit y pulsa Enter. Inspecciona las llaves inicial y final del método. Cambia el nombre del parámetro sin usar _input a input sin cambiar su tipo. Sustituye solo el cuerpo incompleto por tu implementación de las comprobaciones y la confirmación correcta anteriores. Conserva los demás métodos y definiciones de datos. Usa flechas y Backspace/Delete para editar. Guarda con Ctrl+O, Enter y sal con Ctrl+X. La notación ^ de nano significa Ctrl. Si hiciste una edición accidental sin guardar, Ctrl+X y después N a la pregunta de guardado la descarta; no revierte una edición guardada anteriormente.
Lee el método guardado antes de ejecutar:
cat src/state.rs
Utiliza la secuencia fija conocida para observar tus decisiones de estado. --fixture selecciona tiradas publicadas en vez de la fuente aleatoria:
cargo run --quiet -- play --fixture
Espera cada solicitud, introduce una palabra y pulsa Enter:
Primero introduce pairs. Es desconocida: deben mantenerse [2, 2, 4, 4, 6] y rolls 1 tras el rechazo.
Después introduce pair. Puntúa 8 y avanza a [1, 2, 3, 4, 6], rolls 2.
Introduce pair otra vez. Se rechaza la categoría usada, dejando intactos esos dados y rolls 2.
Después introduce straight. Esta elección válida puntúa cero y avanza a la tercera tirada.
Por último introduce sum. Puntúa 20.
El error final esperado es Error: replay not implemented, con estado 1. Los envíos funcionaron, pero replay sigue siendo la función original incompleta, así que la interfaz se niega a guardar una tarjeta sin comprobar. Es el estado intermedio previsto, no una partida guardada correctamente. Si eliges accidentalmente otra categoría legal, termina con las no usadas o pulsa Ctrl+C e inicia intencionalmente una nueva partida fija. Dentro de una partida, un rechazo nunca debe reiniciar ni volver a tirar.
Ante un error del compilador, lee el archivo y la línea indicados, reabre nano, busca el método afectado y corrígelo. Ante un error guardado que no puedas reparar, restaura tu propio archivo anterior a submit y después implementa y prueba submit otra vez:
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-submit.*
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_submit
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_submit/state.rs"
Debe contener el código inicial anterior a submit. 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_submit/state.rs" src/state.rs
Ese comando sustituye intencionalmente solo el archivo de estado editable. La variable de copia es local a esta Terminal; usa las instrucciones condicionales de recuperación en otro shell. No restablezcas setup ni reemplaces la interfaz preparada para corregir un error de estado.
Recalcular y guardar una tarjeta completa
En este paso, implementarás una comprobación independiente de cada ronda registrada antes de aceptar un total. Continúa en la misma Terminal, dentro de /home/labex/project/dice-game; repite los anteriores export PATH y cd si es necesario. Conserva tu método submit completado.
Comprueba primero a mano la tarjeta fija propuesta: pair sobre [2, 2, 4, 4, 6] da 8, straight sobre [1, 2, 3, 4, 6] da 0 y sum sobre [6, 5, 4, 3, 2] da 20. El total recalculado es 28. Si alguien escribiera 10 en vez de 8 en la primera fila, sumar los puntos guardados ocultaría el registro incorrecto. Una tarjeta no puede demostrar su corrección con su propio total o sus puntos guardados.
replay recibe &[Round], un slice: una vista prestada de los registros, sin transferir su propiedad ni modificarlos. Diseña las comprobaciones antes de escribir. Exige exactamente tres registros. Para cada uno, rechaza valores fuera del 1 al 6 antes de llamar a score, porque una implementación de puntuación puede usar un valor como índice de array. Lleva las categorías usadas en un pequeño Vec; contains comprueba si la categoría actual ya apareció. Rechaza duplicados. Recalcula los puntos a partir de los dados y categoría reales y compáralos con los guardados. Añade solo el resultado recalculado al total y devuelve el total únicamente cuando todos los registros sean válidos.
Puedes usar un return Err("...") temprano si falla una condición. En for round in rounds, cada ronda está prestada; round.dice es un pequeño array Copy que puedes examinar sin alterar el registro. La condición !(1..=6).contains(&face) significa que el valor está fuera del rango válido inclusivo. Elige mensajes de rechazo significativos; tu implementación puede organizar estas decisiones de otra forma conservando el contrato.
Guarda un nuevo punto de deshacer que contenga tu submit ya funcional. No debe confundirse con la copia anterior del código inicial:
rs02_before_replay=$(mktemp -d .edit-before-replay.XXXXXX) && cp -p src/state.rs "$rs02_before_replay/state.rs"
Abre el mismo archivo y busca pub fn replay con Ctrl+W:
nano -c src/state.rs
Cambia el nombre del parámetro sin usar _rounds a rounds, conservando su tipo, e implementa solo el cuerpo de esta función. Deja submit, los structs y las conexiones de módulo como estaban. Usa tus comprobaciones ordenadas y acumulador; no devuelvas simplemente 28, no confíes en puntos guardados ni omitas silenciosamente un registro inválido. Guarda con Ctrl+O, Enter y sal con Ctrl+X. Lee el código guardado:
cat src/state.rs
Ejecuta de nuevo la secuencia fija:
cargo run --quiet -- play --fixture
Introduce pair, después straight y luego sum, esperando cada solicitud. El resultado final incluye:
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
La interfaz preparada escribe match.tsv solo después de que una tarjeta completa supere replay. Cada fila tiene cinco valores separados por espacios, un tabulador, la categoría, otro tabulador y puntos. La interfaz preparada gestiona este formato; no implementas un analizador de archivos. Una nueva partida completada reemplaza esta tarjeta del curso; una incompleta deja intacta la anterior.
Lee y reproduce tu archivo guardado real:
cat match.tsv
cargo run --quiet -- replay match.tsv
Deben aparecer los mismos registros y el total 28, sin Saved match.tsv. Replay lee la ruta exacta sin cambiarla ni recurrir a una muestra. Para comprobar que no codificaste en submit la secuencia anterior de categorías, juega una vez más con la secuencia fija:
cargo run --quiet -- play --fixture
Usa straight, sum y después pair. Predice 0, 16 y 0: las tres elecciones son legales y el total recalculado es 16. Esta partida completada reemplaza ahora la tarjeta anterior. Reproduce ese archivo actual:
cargo run --quiet -- replay match.tsv
Sus registros deben reflejar esta segunda secuencia, no la partida anterior de 28 puntos. Se espera otro total porque cambió la regla elegida para cada tirada. El siguiente paso usa dados realmente aleatorios con las mismas decisiones.
Ante un error del compilador, reabre el archivo indicado y corrige la línea de replay señalada. Si hace falta, restaura tu propio punto de control anterior a replay, que conserva submit, y vuelve a implementar replay:
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-replay.*
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_replay
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_replay/state.rs"
Debe contener submit funcional antes de replay. 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_replay/state.rs" src/state.rs
Restaurar el código fuente no cambia una tarjeta existente. Una partida posterior completada solo la reemplaza después de superar replay; las partidas fallidas y la lectura de una tarjeta no lo hacen. La referencia completa siguiente sirve para comparar una función concreta después de tu intento, no para sustituir el módulo como operación normal de aprendizaje.
Referencia completa del estado para comparación y recuperación
Conserva tus campos y el trabajo anterior correcto. Compara solo la función que reparas, edítala en nano y después ejecuta la secuencia fija y reproduce otra vez la tarjeta real.
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)
}
Jugar y reutilizar tu propia partida
En este paso, usarás las reglas terminadas con dados nuevos y te recuperarás de un error habitual en una ruta. Continúa en la misma Terminal, en /home/labex/project/dice-game.
Sin --fixture, la interfaz preparada lee bytes aleatorios de /dev/urandom. Genera cinco valores del 1 al 6 en cada tirada. Es un juego local de práctica. No necesitas alcanzar un total objetivo ni seguir una estrategia ganadora, pero los puntos deben respetar las reglas. Debes utilizar sum, pair y straight una vez cada uno, en cualquier orden. Las palabras rechazadas y las categorías repetidas conservan la tirada actual.
Empieza una partida nueva:
cargo run --quiet -- play
Lee los primeros dados, elige una categoría sin utilizar y pulsa Enter. Repite el proceso en las rondas segunda y tercera. Puedes introducir deliberadamente pairs una vez para ver un rechazo y corregirlo con una categoría disponible. Observa que los dados y el contador rolls no cambian durante la corrección. Tras tres elecciones aceptadas, revisa los tres registros y el total recalculado. Los valores aleatorios pueden diferir de la secuencia fija, y la partida completada reemplaza match.tsv.
Vuelve a leer la nueva tarjeta:
cargo run --quiet -- replay match.tsv
Los registros deben coincidir con tu partida más reciente, incluidos los ceros legítimos. Para practicar un error habitual de ruta, el siguiente comando solicita deliberadamente un archivo inexistente. Espera un mensaje Error: missing-card.tsv: ... y estado 1; no debe producir una tarjeta de sustitución:
cargo run --quiet -- replay missing-card.tsv
Recupérate usando el nombre real:
cargo run --quiet -- replay match.tsv
La tarjeta sigue siendo legible y ninguna reproducción la modifica. Conserva src/main.rs, Cargo.toml y la API pública. La comprobación ejecuta tu implementación original en una copia aislada y lee tu verdadero match.tsv; no crea una tarjeta por ti.
Resumen
Has modelado una partida y sus registros, devuelto éxito o rechazo explícitos y modificado el estado solo después de validarlo. Las elecciones inválidas conservaron la tirada, los ceros legítimos hicieron avanzar la partida y la reproducción recalculó una tarjeta completa. Has jugado con nuevos dados y reutilizado el archivo guardado real sin depender de una VM anterior.



