From 0e7225032a335b911c542642eb52a004cc25f44c Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 15 Sep 2026 20:57:48 -0400 Subject: [PATCH] feat(challenges): agregar 10 retos de Rust + soporte de lenguaje en UI (icono, insignias, selector) (#1) --- .../tamagotchi/code/data/ChallengesData.kt | 158 ++++++++++++++++++ .../code/feature/focus/FocusScreen.kt | 2 +- .../code/feature/learn/LearnScreen.kt | 4 +- .../settings/SettingsLanguageScreen.kt | 2 +- .../code/ui/components/TopicIcons.kt | 2 + app/src/main/res/drawable/ic_lang_rust.xml | 17 ++ 6 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/drawable/ic_lang_rust.xml diff --git a/app/src/main/java/com/tamagotchi/code/data/ChallengesData.kt b/app/src/main/java/com/tamagotchi/code/data/ChallengesData.kt index 3e69a6e..6c02fc6 100644 --- a/app/src/main/java/com/tamagotchi/code/data/ChallengesData.kt +++ b/app/src/main/java/com/tamagotchi/code/data/ChallengesData.kt @@ -1394,5 +1394,163 @@ object ChallengesData { correctAnswerIndex = 0, explanation = "Las funciones asíncronas se definen con 'async def'. Dentro de ellas se usa 'await' para llamar a otras funciones asíncronas." ), + // ========== RUST ========== + CodingChallenge( + id = 201, + language = "Rust", + type = "TRIVIA", + title = "Ownership en asignaciones", + question = "¿Qué ocurre cuando asignas un String a otra variable? let s1 = String::from(\"hola\"); let s2 = s1;", + options = listOf( + "Se copia el contenido y ambas variables son válidas", + "La propiedad (ownership) se mueve a s2; usar s1 después es un error de compilación", + "Ambas apuntan al mismo String y comparten la propiedad", + "s1 se convierte automáticamente en Option" + ), + correctAnswerIndex = 1, + explanation = "En Rust, los tipos con heap (como String) tienen un único dueño. Al asignar s1 a s2, la propiedad se 'mueve' y s1 queda invalidada. El borrow checker lo detecta en tiempo de compilación." + ), + CodingChallenge( + id = 202, + language = "Rust", + type = "PRINT", + title = "Sumando con iteradores", + question = "¿Qué imprime este código?", + codeSnippet = "fn main() {\n let mut v = vec![1, 2, 3];\n v.push(4);\n let total: i32 = v.iter().sum();\n println!(\"{total}\");\n}", + options = listOf( + "10", + "6", + "[1, 2, 3, 4]", + "Error de compilación" + ), + correctAnswerIndex = 0, + explanation = "'iter()' recorre el vector sin tomar posesión (préstamo inmutable) y 'sum()' acumula los elementos: 1+2+3+4 = 10." + ), + CodingChallenge( + id = 203, + language = "Rust", + type = "DEBUG", + title = "Conflicto de préstamos", + question = "Este código no compila. ¿Cuál es el error?", + codeSnippet = "fn main() {\n let mut s = String::from(\"hola\");\n let r1 = &s;\n let r2 = &mut s;\n println!(\"{}, {}\", r1, r2);\n}", + options = listOf( + "No se puede tomar la referencia de un String", + "No puedes tener a la vez una referencia inmutable (&s) y una mutable (&mut s) vivas (E0502)", + "Falta 'ref' en las declaraciones de r1 y r2", + "println! no acepta dos argumentos" + ), + correctAnswerIndex = 1, + explanation = "El borrow checker prohíbe que existan simultáneamente préstamos inmutables y mutables sobre los mismos datos. Es la garantía de Rust contra data races, sin necesidad de runtime (error E0502)." + ), + CodingChallenge( + id = 204, + language = "Rust", + type = "TRIVIA", + title = "El tipo Result", + question = "¿Qué representa Result en Rust?", + options = listOf( + "Un valor que puede ser nulo", + "El resultado de una operación: éxito con un valor T o fallo con un error E", + "Una comparación entre dos valores", + "El retorno de una función asíncrona" + ), + correctAnswerIndex = 1, + explanation = "Result es una enumeración con dos variantes: Ok(T) para el éxito y Err(E) para el fallo. Es la forma idiomática de manejar errores recuperables en Rust (a diferencia de Option, que modela ausencia de valor)." + ), + CodingChallenge( + id = 205, + language = "Rust", + type = "PRINT", + title = "unwrap_or en acción", + question = "¿Qué imprime este código?", + codeSnippet = "fn main() {\n let x: i32 = 10;\n let y: Option = if x > 5 { Some(x) } else { None };\n println!(\"{}\", y.unwrap_or(0));\n}", + options = listOf( + "0", + "Some(10)", + "10", + "Error en tiempo de ejecución" + ), + correctAnswerIndex = 2, + explanation = "Como x > 5, y es Some(10). 'unwrap_or(0)' devuelve el valor interno si es Some, o el valor por defecto (0) si es None. Aquí imprime 10." + ), + CodingChallenge( + id = 206, + language = "Rust", + type = "TRIVIA", + title = "El operador ?", + question = "¿Qué hace el operador '?' en una función que devuelve Result?", + options = listOf( + "Convierte un Err en Ok automáticamente", + "Propaga el error: si es Err, retorna temprano de la función; si es Ok, continúa con el valor", + "Captura la excepción y continúa la ejecución", + "Pregunta al usuario si desea continuar" + ), + correctAnswerIndex = 1, + explanation = "'expr?' es azúcar sintáctico: si el valor es Ok(x) devuelve x, y si es Err(e) hace 'return Err(e)' desde la función actual. Equivale a match { Ok(v) => v, Err(e) => return Err(e) }." + ), + CodingChallenge( + id = 207, + language = "Rust", + type = "PRINT", + title = "String slices", + question = "¿Qué imprime este código?", + codeSnippet = "fn main() {\n let s = String::from(\"rustlang\");\n let slice = &s[0..4];\n println!(\"{slice}\");\n}", + options = listOf( + "rustlang", + "lang", + "rust", + "Error: los índices deben ser negativos" + ), + correctAnswerIndex = 2, + explanation = "'&s[0..4]' crea una rebanadura (slice) de tipo &str con los bytes de 0 a 3 (el límite superior se excluye), es decir, \"rust\"." + ), + CodingChallenge( + id = 208, + language = "Rust", + type = "DEBUG", + title = "Valor movido", + question = "Este código falla al compilar. ¿Cómo se corrige?", + codeSnippet = "fn main() {\n let s = String::from(\"hola\");\n print_len(s);\n println!(\"{s}\");\n}\nfn print_len(text: String) {\n println!(\"{}\", text.len());\n}", + options = listOf( + "Cambiar print_len para que reciba text: &String y llamarlo con print_len(&s)", + "Declarar s como 'let mut s'", + "Copiar s con s.clone() al final del programa", + "No se puede corregir, Rust no permite funciones auxiliares" + ), + correctAnswerIndex = 0, + explanation = "Al pasar 's' por valor, la propiedad se mueve a print_len y s queda inutilizable (E0382). La solución idiomática es prestar el dato: recibir &String (o mejor &str) y llamar con &s." + ), + CodingChallenge( + id = 209, + language = "Rust", + type = "PRINT", + title = "Pipeline de iteradores", + question = "¿Qué imprime este código?", + codeSnippet = "fn main() {\n let v: Vec = (1..=5).filter(|n| n % 2 == 1).map(|n| n * n).collect();\n println!(\"{:?}\", v);\n}", + options = listOf( + "[1, 4, 9, 16, 25]", + "[1, 9, 25]", + "[9, 25]", + "(1..=5)" + ), + correctAnswerIndex = 1, + explanation = "El rango 1..=5 incluye ambos extremos. 'filter' deja los impares (1, 3, 5) y 'map' los eleva al cuadrado: [1, 9, 25]. Los iteradores son perezosos: nada se calcula hasta llamar a collect()." + ), + CodingChallenge( + id = 210, + language = "Rust", + type = "PRINT", + title = "Derivar Debug", + question = "¿Qué imprime este código?", + codeSnippet = "#[derive(Debug)]\nstruct Punto { x: i32, y: i32 }\nfn main() {\n let p = Punto { x: 1, y: 2 };\n println!(\"{p:?}\");\n}", + options = listOf( + "Punto { x: 1, y: 2 }", + "{x: 1, y: 2}", + "Punto(1, 2)", + "Error: struct no implementa Display" + ), + correctAnswerIndex = 0, + explanation = "El formato '{:?}' invoca el trait Debug. Con '#[derive(Debug)]' el compilador genera una implementación que imprime el nombre del tipo y sus campos: Punto { x: 1, y: 2 }." + ), ) } diff --git a/app/src/main/java/com/tamagotchi/code/feature/focus/FocusScreen.kt b/app/src/main/java/com/tamagotchi/code/feature/focus/FocusScreen.kt index 059fd96..69ec6d4 100644 --- a/app/src/main/java/com/tamagotchi/code/feature/focus/FocusScreen.kt +++ b/app/src/main/java/com/tamagotchi/code/feature/focus/FocusScreen.kt @@ -97,7 +97,7 @@ fun TimerPanel( var tempTopic by remember { mutableStateOf("Kotlin") } var showDropdown by remember { mutableStateOf(false) } - val topics = listOf("Kotlin", "JavaScript", "PHP", "Python", "SQL", "Clean Code", "Git", "Estructuras de Datos") + val topics = listOf("Kotlin", "JavaScript", "PHP", "Python", "Rust", "SQL", "Clean Code", "Git", "Estructuras de Datos") Column( modifier = Modifier.fillMaxWidth(), diff --git a/app/src/main/java/com/tamagotchi/code/feature/learn/LearnScreen.kt b/app/src/main/java/com/tamagotchi/code/feature/learn/LearnScreen.kt index bdd4b3a..35b5e43 100644 --- a/app/src/main/java/com/tamagotchi/code/feature/learn/LearnScreen.kt +++ b/app/src/main/java/com/tamagotchi/code/feature/learn/LearnScreen.kt @@ -129,8 +129,8 @@ fun BadgesPanel(viewModel: PetViewModel) { ) Spacer(modifier = Modifier.height(12.dp)) - val languages = listOf("Kotlin", "JavaScript", "PHP", "Python") - val languageKeyMap = mapOf("Kotlin" to "KOTLIN", "JavaScript" to "JS", "PHP" to "PHP", "Python" to "PYTHON") + val languages = listOf("Kotlin", "JavaScript", "PHP", "Python", "Rust") + val languageKeyMap = mapOf("Kotlin" to "KOTLIN", "JavaScript" to "JS", "PHP" to "PHP", "Python" to "PYTHON", "Rust" to "RUST") Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { languages.forEach { langName -> diff --git a/app/src/main/java/com/tamagotchi/code/feature/settings/SettingsLanguageScreen.kt b/app/src/main/java/com/tamagotchi/code/feature/settings/SettingsLanguageScreen.kt index 19bf34e..6e692fb 100644 --- a/app/src/main/java/com/tamagotchi/code/feature/settings/SettingsLanguageScreen.kt +++ b/app/src/main/java/com/tamagotchi/code/feature/settings/SettingsLanguageScreen.kt @@ -39,7 +39,7 @@ fun SettingsLanguageScreen( stringResource(R.string.topic_data_structures) ) - val allLanguages = listOf("Kotlin", "JavaScript", "Python", "PHP") + val allLanguages = listOf("Kotlin", "JavaScript", "Python", "PHP", "Rust") Scaffold( containerColor = Color.Transparent, diff --git a/app/src/main/java/com/tamagotchi/code/ui/components/TopicIcons.kt b/app/src/main/java/com/tamagotchi/code/ui/components/TopicIcons.kt index b5cf5d6..b732287 100644 --- a/app/src/main/java/com/tamagotchi/code/ui/components/TopicIcons.kt +++ b/app/src/main/java/com/tamagotchi/code/ui/components/TopicIcons.kt @@ -12,6 +12,8 @@ fun topicIconRes(topic: String): Int = when { topic.contains("JavaScript", ignoreCase = true) -> R.drawable.ic_lang_javascript topic.contains("Python", ignoreCase = true) -> R.drawable.ic_lang_python topic.contains("PHP", ignoreCase = true) -> R.drawable.ic_lang_php + topic.contains("Rust", ignoreCase = true) -> R.drawable.ic_lang_rust + topic.contains("Go", ignoreCase = true) -> R.drawable.ic_lang_go topic.contains("SQL", ignoreCase = true) -> R.drawable.ic_lang_sql topic.contains("Git", ignoreCase = true) -> R.drawable.ic_lang_git topic.contains("Clean Code", ignoreCase = true) || topic.contains("clean_code", ignoreCase = true) -> R.drawable.ic_lang_cleancode diff --git a/app/src/main/res/drawable/ic_lang_rust.xml b/app/src/main/res/drawable/ic_lang_rust.xml new file mode 100644 index 0000000..0c9546d --- /dev/null +++ b/app/src/main/res/drawable/ic_lang_rust.xml @@ -0,0 +1,17 @@ + + + + + +