diff --git a/1r/Q1/PRO1/simulador-parcials/.gitignore b/1r/Q1/PRO1/simulador-parcials/.gitignore index 5059323..e94eba0 100644 --- a/1r/Q1/PRO1/simulador-parcials/.gitignore +++ b/1r/Q1/PRO1/simulador-parcials/.gitignore @@ -4,3 +4,7 @@ # Binarios compilados en make grade (quedan dentro de cada carpeta de intento). intentos/*/*.bin + +# Estado de la sesion interactiva (cronometro: start/end time). Es runtime, +# no forma parte del intento en si (el .cpp del usuario si se conserva). +intentos/*/.estado diff --git a/1r/Q1/PRO1/simulador-parcials/Makefile b/1r/Q1/PRO1/simulador-parcials/Makefile index e95d2e2..b898810 100644 --- a/1r/Q1/PRO1/simulador-parcials/Makefile +++ b/1r/Q1/PRO1/simulador-parcials/Makefile @@ -13,13 +13,19 @@ all: start help: @echo "Simulador de parciales de PRO1 (no oficial)" @echo "" - @echo " make / make start Descarga (si falta) la cache de parciales," - @echo " elige uno al azar, muestra sus enunciados," - @echo " crea un intento nuevo en $(INTENTOS_DIR)/ y" - @echo " arranca el cronometro (EXAM_MINUTES=$(EXAM_MINUTES))." - @echo " make grade Compila y ejecuta cada .cpp del ultimo intento" - @echo " (o del indicado con ATTEMPT=) y muestra" - @echo " la solucion de referencia para comparar a ojo." + @echo " make / make start Descarga (si falta) la cache de parciales y" + @echo " arranca una sesion de examen en MODO REAL:" + @echo " disclaimer, confirmacion, parcial al azar," + @echo " intento nuevo en $(INTENTOS_DIR)/ y una sesion" + @echo " interactiva (examshell>) con cronometro real" + @echo " (EXAM_MINUTES=$(EXAM_MINUTES)). Ver 'help' dentro" + @echo " de la sesion para los comandos (status, grademe," + @echo " enunciado, finish...)." + @echo " make grade Atajo fuera de sesion: compila y ejecuta cada" + @echo " .cpp del ultimo intento (o del indicado con" + @echo " ATTEMPT=) y muestra la solucion de" + @echo " referencia para comparar a ojo. Util para" + @echo " repasar despues de terminar la sesion." @echo " NO es correccion automatica pass/fail." @echo " make clean Borra la cache descargada y los binarios" @echo " compilados. NO toca $(INTENTOS_DIR)/ (tu trabajo)." diff --git a/1r/Q1/PRO1/simulador-parcials/README.md b/1r/Q1/PRO1/simulador-parcials/README.md index d7f0222..1150f76 100644 --- a/1r/Q1/PRO1/simulador-parcials/README.md +++ b/1r/Q1/PRO1/simulador-parcials/README.md @@ -38,10 +38,8 @@ en el repo original. ## Uso ```bash -make # = make start: descarga cache si falta, elige parcial al azar, - # muestra enunciados, crea intento nuevo, arranca cronómetro -make grade # compila y ejecuta (interactivo) el último intento, y muestra - # la solución de referencia de cada problema para comparar +make # = make start: arranca una sesión de examen en MODO REAL +make grade # atajo FUERA de sesión: corrige el último intento (ver abajo) make clean # borra cache y binarios compilados (NO toca intentos/) make help # lista de comandos ``` @@ -54,17 +52,45 @@ Variables: - `ATTEMPT` — ruta del intento a corregir con `make grade` (por defecto, el más reciente en `intentos/`). -Al ejecutar `make`/`make start`: -1. Si no hay cache local de los 7 parciales, los descarga. -2. Elige uno al azar entre los disponibles en cache. -3. Muestra de golpe los enunciados de todos sus problemas (como el examen - real, no de uno en uno). -4. Crea `intentos/-/` con un `.cpp` vacío por problema - (cabecera ASCII estándar de 42 ya puesta, nombrado `.cpp`). -5. Arranca el cronómetro en terminal. Ctrl+C para pararlo cuando acabes (o - déjalo llegar a 0). - -Al ejecutar `make grade`: +### `make` / `make start`: sesión en modo REAL + +Inspirado en el modo "real" de Maestro42 (examshell de 42): no es un script +de un solo disparo, es una **sesión interactiva persistente** con cronómetro +real, igual de fiel al examen oficial como sea posible: + +1. Si no hay cache local de los 7 parciales, la descarga. +2. Disclaimer: qué es esto, qué no es, qué comandos hay disponibles durante + la sesión. Pide confirmación (`y/n`) antes de arrancar nada. +3. Animación de "conexión" (puramente estética). +4. Elige un parcial al azar entre los disponibles en cache, crea + `intentos/-/` con un `.cpp` vacío por problema + (cabecera ASCII estándar de 42 ya puesta, nombrado `.cpp`), y + pide pulsar una tecla para comenzar. **El cronómetro arranca de verdad en + ese momento**, no antes. +5. Muestra de golpe los enunciados de todos los problemas (como el examen + real, no de uno en uno) y entra en el prompt interactivo `examshell>`, + que se queda esperando comandos hasta que termines o se acabe el tiempo: + + | Comando | Qué hace | + |---|---| + | `help` | Lista de comandos. | + | `status` | Tiempo restante real, parcial actual y estado de compilación de cada problema. | + | `enunciado` (alias `subject`) | Vuelve a imprimir los enunciados completos. | + | `grademe` | Compila, ejecuta (interactivo) y compara cada `.cpp` con la solución de referencia — ver detalle abajo. | + | `finish` | Pide confirmación (`yes`) y muestra el resumen final (tiempo usado, estado de cada problema) antes de salir. | + + Si el tiempo ya se acabó cuando ejecutas cualquier comando, la sesión lo + avisa ("SE ACABÓ EL TIEMPO") y fuerza el cierre con resumen, no deja + seguir trabajando como si nada. Ctrl+C / Ctrl+D también cierran la + sesión de forma limpia (resumen incluido), sin pedir confirmación. + +### `grademe` (dentro de sesión) / `make grade` (fuera de sesión) + +Misma lógica de corrección en los dos casos — `grademe` es simplemente el +comando interno para invocarla sin salir de la sesión; `make grade` sigue +disponible como atajo standalone para repasar después de que la sesión ya +haya terminado: + - Compila cada `.cpp` del intento con los mismos flags que el resto del repo (`clang++ -Wall -Wextra -fsanitize=address,undefined -O0`). - Ejecuta tu binario de forma **interactiva**: tú introduces tu propia @@ -83,8 +109,9 @@ simulador-parcials/ ├── scripts/ │ ├── common.sh # config compartida (lista de parciales, slug()) │ ├── fetch_examenes.sh # descarga y separa enunciado/solución por problema -│ ├── iniciar_examen.sh # elige parcial, muestra enunciados, crea intento, cronómetro -│ ├── grade.sh # compila, ejecuta y muestra solución de referencia +│ ├── iniciar_examen.sh # disclaimer, confirmación, elige parcial, crea intento, arranca cronómetro +│ ├── sesion_examen.sh # bucle interactivo examshell> (status/enunciado/grademe/finish) +│ ├── grade.sh # compila, ejecuta y muestra solución de referencia (grademe / make grade) │ └── header_template.cpp # plantilla de la cabecera 42 (placeholders de nombre/fecha) ├── .cache/ # (generado, gitignored) enunciados y soluciones descargados └── intentos/ # (generado, SÍ trackeado) tus .cpp de cada intento diff --git a/1r/Q1/PRO1/simulador-parcials/scripts/iniciar_examen.sh b/1r/Q1/PRO1/simulador-parcials/scripts/iniciar_examen.sh index 1fed12c..e5eefc2 100755 --- a/1r/Q1/PRO1/simulador-parcials/scripts/iniciar_examen.sh +++ b/1r/Q1/PRO1/simulador-parcials/scripts/iniciar_examen.sh @@ -1,9 +1,10 @@ #!/usr/bin/env bash -# Elige un parcial al azar de los que hay en cache, muestra los enunciados de -# todos sus problemas de golpe (como el examen real), crea una carpeta de -# intento con un .cpp vacio por problema (cabecera 42 incluida) y arranca un -# cronometro visible en terminal. -set -euo pipefail +# Arranca una sesion de examen en modo REAL: disclaimer, confirmacion, +# animacion de "conexion", seleccion de parcial al azar, creacion del +# intento (.cpp con cabecera 42 por problema) y entrada en el bucle +# interactivo de comandos (examshell>). El bucle en si vive en +# scripts/sesion_examen.sh. +set -uo pipefail DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$DIR/common.sh" @@ -12,7 +13,37 @@ CACHE_DIR="${1:-.cache}" INTENTOS_DIR="${2:-intentos}" EXAM_MINUTES="${3:-90}" -# Solo se puede elegir entre los parciales que ya tengan cache poblada. +cat <<'EOF' +================================================================== + SIMULADOR DE PARCIALES DE PRO1 -- MODO REAL (no oficial) +================================================================== + +Herramienta personal, NO oficial de la FIB/UPC, inspirada en el modo +"real" de Maestro42: en cuanto confirmes, el tiempo empieza a contar +de verdad y entraras en una sesion interactiva (examshell>) con estos +comandos disponibles durante el examen: + + help Lista de comandos. + status Tiempo restante, parcial actual y estado de cada problema. + enunciado Vuelve a mostrar los enunciados completos del parcial. + grademe Compila, ejecuta y compara con la solucion de referencia. + finish Termina la sesion (pide confirmacion) y muestra el resumen. + +No hay pass/fail automatico (PRO1 no tiene jutge con casos de test +para estos examenes): 'grademe' solo ayuda a la autocorreccion manual. +================================================================== +EOF +echo +read -e -p "¿Empezar un parcial aleatorio en modo REAL ahora? (y/n) " confirm +case "$confirm" in + y|Y|yes|YES) ;; + *) + echo "Cancelado." + exit 0 + ;; +esac + +# --- seleccion de parcial: solo entre los que ya tengan cache poblada --- disponibles=() for parcial in "${PARCIALES[@]}"; do pslug=$(slug "$parcial") @@ -31,28 +62,25 @@ parcial="${disponibles[$idx]}" pslug=$(slug "$parcial") pdir="$CACHE_DIR/$pslug" -echo "==================================================================" -echo " PARCIAL SELECCIONADO: $parcial" -echo "==================================================================" +# --- animacion de "conexion" (puramente estetica, no afecta al cronometro) --- echo - -problemas=("$pdir"/*/) -for probdir in "${problemas[@]}"; do - code=$(basename "$probdir") - echo "------------------------------------------------------------------" - echo " Problema $code" - echo "------------------------------------------------------------------" - if [ -s "$probdir/enunciado.txt" ]; then - cat "$probdir/enunciado.txt" - else - echo "(Este fichero fuente no trae un enunciado capturable como" - echo " comentario inicial; revisa el .cc original en el repo de" - echo " referencia si necesitas el enunciado completo.)" - fi +type_out() { + local text="$1" c + while IFS= read -r -n1 c; do + printf '%s' "$c" + sleep 0.01 + done <<< "$text" echo -done +} +type_out "Conectando con examshell..." +sleep 0.2 +type_out "Autenticando a agarcia2@pro1-simulador..." +sleep 0.2 +type_out "Sesion establecida." +echo -# Crea la carpeta de intento con un .cpp vacio por problema. +# --- creacion del intento: un .cpp con cabecera 42 por problema --- +problemas=("$pdir"/*/) timestamp=$(date +%Y%m%d-%H%M%S) intento_dir="$INTENTOS_DIR/${timestamp}-${pslug}" mkdir -p "$intento_dir" @@ -67,7 +95,7 @@ for probdir in "${problemas[@]}"; do # barras, y con '/' como delimitador el sed de BSD (macOS) rompe. sed -e "s#XXXXXXXXXX#${filename}#" -e "s#YYYY/MM/DD HH:MM:SS#${fecha}#g" \ "$DIR/header_template.cpp" > "$dest" - cat >> "$dest" <<'EOF' + cat >> "$dest" <<'CPPEOF' #include @@ -75,26 +103,27 @@ int main(void) { return (0); } -EOF +CPPEOF done echo "==================================================================" +echo " Vas a empezar el parcial: $parcial" +echo " Modo REAL: tendras $EXAM_MINUTES minutos en cuanto pulses una tecla." echo " Intento creado en: $intento_dir" -echo " Tienes $EXAM_MINUTES minutos. Cronometro en marcha." -echo " Cuando acabes (o se acabe el tiempo): Ctrl+C y luego 'make grade'." -echo "==================================================================" +read -e -n1 -p "Pulsa una tecla para comenzar... " tecla +echo echo -total_seg=$((EXAM_MINUTES * 60)) -inicio=$(date +%s) -while :; do - ahora=$(date +%s) - transcurrido=$((ahora - inicio)) - restante=$((total_seg - transcurrido)) - if [ "$restante" -le 0 ]; then - printf "\r¡TIEMPO AGOTADO! \n" - break - fi - printf "\rTiempo restante: %02d:%02d " $((restante / 60)) $((restante % 60)) - sleep 1 -done +# --- arranque real del cronometro: a partir de aqui el tiempo cuenta --- +start_time=$(date +%s) +end_time=$((start_time + EXAM_MINUTES * 60)) + +cat > "$intento_dir/.estado" <"), persistente mientras quede tiempo. Se invoca desde +# iniciar_examen.sh justo despues de arrancar el cronometro real (lee +# el estado de /.estado). Tambien se puede reanudar a mano +# sobre un intento ya empezado: +# bash scripts/sesion_examen.sh +set -uo pipefail + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$DIR/common.sh" + +CACHE_DIR="${1:-.cache}" +INTENTOS_DIR="${2:-intentos}" +INTENTO_DIR="${3:?falta la ruta del intento}" + +ESTADO="$INTENTO_DIR/.estado" +if [ ! -f "$ESTADO" ]; then + echo "No hay sesion activa en $INTENTO_DIR (falta .estado)." >&2 + exit 1 +fi +source "$ESTADO" + +PDIR="$CACHE_DIR/$PARCIAL_SLUG" + +tiempo_restante() { + local ahora restante + ahora=$(date +%s) + restante=$((END_TIME - ahora)) + [ "$restante" -lt 0 ] && restante=0 + echo "$restante" +} + +tiempo_agotado() { + local ahora + ahora=$(date +%s) + [ "$ahora" -ge "$END_TIME" ] +} + +fmt_mmss() { + printf "%02d:%02d" $(($1 / 60)) $(($1 % 60)) +} + +mostrar_enunciados() { + for probdir in "$PDIR"/*/; do + code=$(basename "$probdir") + echo "------------------------------------------------------------------" + echo " Problema $code" + echo "------------------------------------------------------------------" + if [ -s "$probdir/enunciado.txt" ]; then + cat "$probdir/enunciado.txt" + else + echo "(Este fichero fuente no trae un enunciado capturable como" + echo " comentario inicial; revisa el .cc original en el repo de" + echo " referencia si necesitas el enunciado completo.)" + fi + echo + done +} + +estado_compilacion() { + # Compila en silencio cada .cpp del intento y muestra OK/ERROR. + shopt -s nullglob + local cpps=("$INTENTO_DIR"/*.cpp) cpp code + shopt -u nullglob + for cpp in "${cpps[@]}"; do + code=$(basename "$cpp" .cpp) + if clang++ -Wall -Wextra -fsanitize=address,undefined -O0 "$cpp" -o "$INTENTO_DIR/${code}.bin" 2>/dev/null; then + echo " $code : compila OK" + else + echo " $code : ERROR DE COMPILACION" + fi + done +} + +mostrar_help() { + cat <<'EOF' +Comandos disponibles: + help Esta ayuda. + status Tiempo restante, parcial actual y estado de cada problema. + enunciado Vuelve a mostrar los enunciados completos del parcial (alias: subject). + grademe Compila, ejecuta (interactivo) y compara cada .cpp con la + solucion de referencia. NO es correccion automatica pass/fail. + finish Termina la sesion (pide confirmacion 'yes') y muestra el resumen. +EOF +} + +mostrar_status() { + local restante + restante=$(tiempo_restante) + echo "Parcial: $PARCIAL" + echo "Tiempo restante: $(fmt_mmss "$restante") de $EXAM_MINUTES min" + echo "Enunciados en: $PDIR/*/enunciado.txt" + echo "Tus .cpp en: $INTENTO_DIR/" + echo "Estado de los problemas:" + estado_compilacion +} + +resumen_final() { + local ahora usado tope + ahora=$(date +%s) + usado=$((ahora - START_TIME)) + tope=$((EXAM_MINUTES * 60)) + [ "$usado" -gt "$tope" ] && usado=$tope + echo "==================================================================" + echo " RESUMEN FINAL" + echo "==================================================================" + echo " Parcial: $PARCIAL" + echo " Tiempo usado: $(fmt_mmss "$usado") de $EXAM_MINUTES min" + echo " Estado final de los problemas:" + estado_compilacion + echo "==================================================================" +} + +# finish : cierra la sesion. En modo "manual" pide +# confirmacion explicita; en "timeup"/"eof" cierra directamente (no hay +# nadie a quien pedirle confirmacion: se acabo el tiempo o se corto la +# entrada). Limpia los binarios temporales de 'grademe'/'status' pero +# nunca los .cpp del usuario. +finish() { + local modo="${1:-manual}" + case "$modo" in + manual) + read -e -p "Escribe 'yes' para confirmar que quieres terminar: " conf + if [ "$conf" != "yes" ]; then + echo "Cancelado, sigues en la sesion." + return 1 + fi + ;; + timeup) + echo + echo "¡¡SE ACABO EL TIEMPO!! Cerrando la sesion automaticamente." + ;; + eof) + echo + echo "Sesion interrumpida (EOF o señal de salida). Cerrando." + ;; + esac + resumen_final + rm -f "$INTENTO_DIR"/*.bin 2>/dev/null + exit 0 +} + +trap 'finish eof' INT TERM + +echo "==================================================================" +echo " Parcial: $PARCIAL -- tienes $EXAM_MINUTES minutos. examshell>" +echo " Escribe 'help' para ver los comandos disponibles." +echo "==================================================================" +mostrar_enunciados + +while true; do + if tiempo_agotado; then + finish timeup + fi + + if ! read -e -p "examshell> " cmd; then + finish eof + fi + + if tiempo_agotado; then + finish timeup + fi + + case "$cmd" in + help) mostrar_help ;; + status) mostrar_status ;; + enunciado | subject) mostrar_enunciados ;; + grademe) bash "$DIR/grade.sh" "$CACHE_DIR" "$INTENTOS_DIR" "$INTENTO_DIR" ;; + finish) finish manual || true ;; + "") ;; + *) echo "Comando desconocido, usa 'help' para ver los comandos disponibles." ;; + esac +done