Apuntes DAM
Volver al inicio

Ejercicios resueltos de Programación Multimedia y Dispositivos Móviles

Los 15 ejercicios de Programación Multimedia y Dispositivos Móviles de la web, tema a tema: cada uno con su enunciado, los datos que necesitas (código de partida, ejemplos de entrada y salida o la base de datos) y, al final, la solución explicada.

Descargar el PDF

Tecnologías y Plataformas Móviles

1. Densidades de pantalla y recursos de Android

Medio · Tecnologías y Plataformas Móviles · apuntesdam.com/subject/prog-multimedia/topic/tecnologias-plataformas-movil

Android agrupa las pantallas por densidad para escoger imágenes y por anchura mínima (smallest width) para escoger diseños. Cada línea es un dispositivo «nombre anchoPx altoPx pulgadas». Calcula su densidad real (diagonal en píxeles / pulgadas), el grupo de densidad más cercano (ldpi 120, mdpi 160, hdpi 240, xhdpi 320, xxhdpi 480, xxxhdpi 640; ante empate, el menor), el factor de escala (dpi del grupo / 160), su tamaño en dp (lado corto y lado largo divididos por el factor, redondeados), si es «móvil», «tableta» (lado corto ≥ 600 dp) o «tableta grande» (≥ 720 dp) con su carpeta de diseños (layout, layout-sw600dp o layout-sw720dp) y cuántos píxeles mide un icono de 48 dp. Formato: «nombre: D dpi → grupo (×F), A × L dp, tipo → carpeta, icono 48 dp = N px», con el factor sin «.0» si es entero (×1.5, ×2).

Código de partida (java)
import java.util.*; public class Main {    static final String[] NOMBRES = {"ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"};    static final int[] DPI = {120, 160, 240, 320, 480, 640};     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        while (sc.hasNextLine()) {            String[] p = sc.nextLine().trim().split(" ");      // nombre anchoPx altoPx pulgadas            if (p.length < 4) continue;            // TODO: calcula densidad, tamaño en dp y carpeta de recursos        }    }}

Ejemplo: Móviles y tableta

Entrada

Pixel7 1080 2400 6.3
GalaxyTabS9 1600 2560 11
MovilBarato 720 1600 6.5

Salida esperada

Pixel7: 418 dpi → xxhdpi (×3), 360 × 800 dp, móvil → layout, icono 48 dp = 144 px
GalaxyTabS9: 274 dpi → hdpi (×1.5), 1067 × 1707 dp, tableta grande → layout-sw720dp, icono 48 dp = 72 px
MovilBarato: 270 dpi → hdpi (×1.5), 480 × 1067 dp, móvil → layout, icono 48 dp = 72 px

2. El ciclo de vida de una Activity

Difícil · Tecnologías y Plataformas Móviles · apuntesdam.com/subject/prog-multimedia/topic/tecnologias-plataformas-movil

Entender cuándo llama Android a cada método del ciclo de vida evita perder datos. Cada línea es un evento; simula las llamadas y el estado de la pantalla con un campo de texto: «abrir» (onCreate → onStart → onResume), «escribir TEXTO» (el usuario escribe, sin llamadas), «dialogo» (un diálogo encima: onPause si estaba reanudada) y «cerrar_dialogo» (onResume si estaba pausada), «home» (onPause si estaba reanudada, onStop, onSaveInstanceState: se guarda el texto), «volver» (si estaba detenida: onRestart → onStart → onResume), «girar» (se destruye y se recrea: onPause → onStop → onSaveInstanceState → onDestroy → onCreate → onStart → onRestoreInstanceState → onResume, y el texto se recupera de lo guardado), «matar_proceso» (si estaba detenida, el sistema elimina el proceso: «(proceso eliminado sin más llamadas)», estado NINGUNO y el texto en memoria se pierde), «reabrir» (onCreate → onStart → onRestoreInstanceState → onResume, recuperando lo guardado) y «atras» (onPause si estaba reanudada, onStop, onDestroy: estado DESTRUIDA y no se guarda nada). Muestra «evento: llamadas separadas por → | estado ESTADO | texto «texto»» (para «escribir», la llamada es «(el usuario escribe «texto»)»; para un evento desconocido, «(evento desconocido)»).

Código de partida (java)
import java.util.*; public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        // Estados: NINGUNO, CREADA, INICIADA, REANUDADA, PAUSADA, DETENIDA, DESTRUIDA        // TODO: simula el ciclo de vida de una Activity ante cada evento    }}

Ejemplo: Uso normal

Entrada

abrir
escribir Hola
dialogo
cerrar_dialogo
home
volver
atras

Salida esperada

abrir: onCreate → onStart → onResume | estado REANUDADA | texto «»
escribir: (el usuario escribe «Hola») | estado REANUDADA | texto «Hola»
dialogo: onPause | estado PAUSADA | texto «Hola»
cerrar_dialogo: onResume | estado REANUDADA | texto «Hola»
home: onPause → onStop → onSaveInstanceState | estado DETENIDA | texto «Hola»
volver: onRestart → onStart → onResume | estado REANUDADA | texto «Hola»
atras: onPause → onStop → onDestroy | estado DESTRUIDA | texto «»

Ejemplo: Giro y proceso eliminado

Entrada

abrir
escribir Pedido 42
girar
home
matar_proceso
reabrir

Salida esperada

abrir: onCreate → onStart → onResume | estado REANUDADA | texto «»
escribir: (el usuario escribe «Pedido 42») | estado REANUDADA | texto «Pedido 42»
girar: onPause → onStop → onSaveInstanceState → onDestroy → onCreate → onStart → onRestoreInstanceState → onResume | estado REANUDADA | texto «Pedido 42»
home: onPause → onStop → onSaveInstanceState | estado DETENIDA | texto «Pedido 42»
matar_proceso: (proceso eliminado sin más llamadas) | estado NINGUNO | texto «»
reabrir: onCreate → onStart → onRestoreInstanceState → onResume | estado REANUDADA | texto «Pedido 42»

3. Elegir el minSdk de una app

Muy difícil · Tecnologías y Plataformas Móviles · apuntesdam.com/subject/prog-multimedia/topic/tecnologias-plataformas-movil

Subir el minSdk permite usar APIs más nuevas sin comprobaciones, pero deja fuera a quienes tienen móviles antiguos. La entrada tiene líneas «cuota NIVEL PORCENTAJE» (dispositivos activos con cada nivel de API; puede que no sumen 100, así que hay que normalizar), «api NOMBRE NIVEL» (APIs que usa la app y el nivel en que aparecieron), «minSdk N» y «objetivo P». Muestra «minSdk N: X % de los dispositivos» (los de nivel ≥ N, 1 decimal); el minSdk más alto que aún cubre al menos el objetivo: «Recomendado para el P %: minSdk M (X %)» (P sin decimales) o «Ningún minSdk alcanza el P %»; y para cada API, «NOMBRE: disponible siempre (API L)» si L ≤ minSdk, o «NOMBRE: comprobar Build.VERSION.SDK_INT >= L (no disponible en el X % de los dispositivos)» con el porcentaje de dispositivos entre minSdk y L − 1.

Código de partida (java)
import java.util.*; public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        TreeMap<Integer, Double> cuota = new TreeMap<>();            // nivel de API → % de dispositivos activos        List<String[]> apis = new ArrayList<>();                    // nombre nivelMinimo        int minSdk = 0;        double objetivo = 0;        while (sc.hasNextLine()) {            String[] p = sc.nextLine().trim().split(" ");            switch (p[0]) {                case "cuota" -> cuota.put(Integer.parseInt(p[1]), Double.parseDouble(p[2]));                case "api" -> apis.add(new String[]{p[1], p[2]});                case "minSdk" -> minSdk = Integer.parseInt(p[1]);                case "objetivo" -> objetivo = Double.parseDouble(p[1]);                default -> { }            }        }        // TODO: cobertura del minSdk, minSdk recomendado y comprobaciones de versión necesarias    }}

Ejemplo: App de pagos

Entrada

cuota 24 2.0
cuota 26 4.0
cuota 28 6.5
cuota 29 9.0
cuota 30 13.5
cuota 31 15.0
cuota 33 20.0
cuota 34 18.0
cuota 35 12.0
api Notificaciones 26
api PhotoPicker 33
api PermisoNotificaciones 33
minSdk 26
objetivo 95

Salida esperada

minSdk 26: 98.0 % de los dispositivos
Recomendado para el 95 %: minSdk 26 (98.0 %)
Notificaciones: disponible siempre (API 26)
PhotoPicker: comprobar Build.VERSION.SDK_INT >= 33 (no disponible en el 48.0 % de los dispositivos)
PermisoNotificaciones: comprobar Build.VERSION.SDK_INT >= 33 (no disponible en el 48.0 % de los dispositivos)

Desarrollo de Aplicaciones Móviles

4. Las diferencias de una lista, como DiffUtil

Medio · Desarrollo de Aplicaciones Móviles · apuntesdam.com/subject/prog-multimedia/topic/desarrollo-aplicaciones-movil

Para no redibujar una lista entera (RecyclerView o LazyColumn) cada vez que cambian los datos, Android calcula las diferencias entre la lista vieja y la nueva (DiffUtil, ListAdapter). La primera línea es la lista antigua y la segunda la nueva, como elementos «id:contenido» separados por espacios (cualquiera puede estar vacía). Muestra, primero, «notifyItemRemoved: id» por cada elemento que ya no está (en el orden de la lista vieja); después, recorriendo la lista nueva, «notifyItemInserted(posición): id» para los nuevos y «notifyItemChanged(posición): id viejo → nuevo» para los que cambian de contenido. Termina con «Sin cambios: A · Insertados: B · Borrados: C · Modificados: D» (añadiendo « · Hay elementos movidos» si los elementos que siguen en ambas listas cambian de orden relativo) y con «No hace falta redibujar» o «Se redibujan N de M filas (notifyDataSetChanged redibujaría M)», donde N son insertados más modificados.

Código de partida (java)
import java.util.*; public class Main {    record Elemento(String id, String contenido) { }     static List<Elemento> leer(String linea) {        List<Elemento> l = new ArrayList<>();        if (linea.isBlank()) return l;        for (String par : linea.trim().split(" ")) {            String[] p = par.split(":");            l.add(new Elemento(p[0], p[1]));        }        return l;    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        List<Elemento> antes = leer(sc.nextLine()), despues = leer(sc.hasNextLine() ? sc.nextLine() : "");        // TODO: calcula los cambios entre las dos listas como DiffUtil    }}

Ejemplo: Lista de tareas

Entrada

1:Pan 2:Leche 3:Huevos 4:Fruta
1:Pan 3:Huevos(12) 5:Café 4:Fruta

Salida esperada

notifyItemRemoved: 2
notifyItemChanged(1): 3 Huevos → Huevos(12)
notifyItemInserted(2): 5
Sin cambios: 2 · Insertados: 1 · Borrados: 1 · Modificados: 1
Se redibujan 2 de 4 filas (notifyDataSetChanged redibujaría 4)

Ejemplo: Reordenada

Entrada

a:x b:y c:z
c:z a:x b:y

Salida esperada

Sin cambios: 3 · Insertados: 0 · Borrados: 0 · Modificados: 0 · Hay elementos movidos
Se redibujan 0 de 3 filas (notifyDataSetChanged redibujaría 3)

Ejemplo: Sin cambios

Entrada

a:1 b:2
a:1 b:2

Salida esperada

Sin cambios: 2 · Insertados: 0 · Borrados: 0 · Modificados: 0
No hace falta redibujar

5. La pila de navegación de una app

Difícil · Desarrollo de Aplicaciones Móviles · apuntesdam.com/subject/prog-multimedia/topic/desarrollo-aplicaciones-movil

El componente Navigation de Android mantiene una pila de pantallas (back stack). La primera línea es «inicio DESTINO»; después vienen órdenes «navigate DESTINO [popUpTo=X] [inclusive] [singleTop]» y «back». navigate apila el destino; con popUpTo=X, antes se desapilan las pantallas hasta dejar X en la cima (con inclusive, también X), lo que se usa por ejemplo tras iniciar sesión para que «atrás» no vuelva al acceso; con singleTop, si el destino ya está en la cima no se apila otra vez («orden: ya estás en DESTINO (singleTop)»). Si X no está en la pila, «ERROR: X no está en la pila» y no se hace nada. back desapila; si solo queda una pantalla, «back: se cierra la app desde DESTINO» y se termina. Tras cada orden que cambia la pila (y tras un back que no cierra), muestra la orden y «→ [base > … > cima]».

Código de partida (java)
import java.util.*; public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        Deque<String> pila = new ArrayDeque<>();      // la cima es la pantalla visible        pila.push(sc.nextLine().trim().split(" ")[1]);  // inicio DESTINO        // TODO: procesa navigate y back como el componente Navigation    }}

Ejemplo: Inicio de sesión

Entrada

inicio splash
navigate login popUpTo=splash inclusive
navigate inicio popUpTo=login inclusive
navigate detalle
navigate detalle singleTop
navigate carrito
back
back
back

Salida esperada

navigate login popUpTo=splash inclusive → [login]
navigate inicio popUpTo=login inclusive → [inicio]
navigate detalle → [inicio > detalle]
navigate detalle singleTop: ya estás en detalle (singleTop)
navigate carrito → [inicio > detalle > carrito]
back → [inicio > detalle]
back → [inicio]
back: se cierra la app desde inicio

6. Sincronización sin conexión con conflictos

Muy difícil · Desarrollo de Aplicaciones Móviles · apuntesdam.com/subject/prog-multimedia/topic/desarrollo-aplicaciones-movil

Una app «offline first» guarda los cambios hechos sin conexión y los envía al recuperarla. La entrada tiene tres secciones: [servidor] con el estado actual «id valor tiempo», [base] con la versión que tenía el móvil cuando se quedó sin conexión, y [cambios] con las operaciones pendientes «crear id valor tiempo», «editar id valor tiempo» o «borrar id tiempo» (los valores no tienen espacios). Para cada cambio, en orden: crear algo que ya existe → «CONFLICTO id: ya existe en el servidor (se conserva «valor»)»; editar o borrar algo que ya no existe → «IGNORADO id: ya no existe en el servidor»; si el elemento ha cambiado en el servidor desde la base (distinto tiempo) y el cambio del servidor es más reciente → «CONFLICTO id: gana el servidor «valor» (tiempo) frente al cambio local (tiempo)» (estrategia «gana el último»); en otro caso se aplica: «CREADO id = «valor»», «BORRADO id» o «EDITADO id = «valor»», añadiendo « (gana el cambio local, más reciente)» o « (sobre un cambio del servidor más antiguo)» si había cambio en el servidor. Termina con «Servidor tras sincronizar:», cada elemento « id = «valor» (tiempo)» por orden de id y «Aplicados: A · Conflictos: C».

Código de partida (java)
import java.util.*; public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        Map<String, String[]> servidor = new TreeMap<>(), local = new TreeMap<>();   // id → {valor, marca de tiempo}        List<String[]> pendientes = new ArrayList<>();                            // cambios hechos sin conexión        String seccion = "";        while (sc.hasNextLine()) {            String l = sc.nextLine().trim();            if (l.isEmpty()) continue;            if (l.startsWith("[")) { seccion = l; continue; }            String[] p = l.split(" ");            switch (seccion) {                case "[servidor]" -> servidor.put(p[0], new String[]{p[1], p[2]});                case "[base]" -> local.put(p[0], new String[]{p[1], p[2]});                case "[cambios]" -> pendientes.add(p);              // op id valor tiempo  (op: crear, editar, borrar)                default -> { }            }        }        // TODO: aplica los cambios pendientes al servidor resolviendo conflictos y muestra el resultado    }}

Ejemplo: Tareas

Entrada

[servidor]
t1 Comprar_pan 100
t2 Llamar_al_banco 250
t3 Entregar_la_práctica 120
[base]
t1 Comprar_pan 100
t2 Llamar_al_banco 110
t3 Entregar_la_práctica 120
[cambios]
editar t1 Comprar_pan_integral 300
editar t2 Llamar_a_las_9 200
crear t4 Recoger_paquete 210
borrar t3 220
crear t1 Duplicada 230

Salida esperada

EDITADO t1 = «Comprar_pan_integral»
CONFLICTO t2: gana el servidor «Llamar_al_banco» (250) frente al cambio local (200)
CREADO t4 = «Recoger_paquete»
BORRADO t3
CONFLICTO t1: ya existe en el servidor (se conserva «Comprar_pan_integral»)
Servidor tras sincronizar:
  t1 = «Comprar_pan_integral» (300)
  t2 = «Llamar_al_banco» (250)
  t4 = «Recoger_paquete» (210)
Aplicados: 3 · Conflictos: 2

Motores de Juegos

7. Física con paso fijo, acumulador e interpolación

Medio · Motores de Juegos · apuntesdam.com/subject/prog-multimedia/topic/motores-juegos

Los motores dibujan a la frecuencia que pueden, pero calculan la física en pasos fijos (FixedUpdate en Unity, _physics_process en Godot). La entrada tiene «paso P» (ms de cada paso de física), «velocidad V» (px/s de un objeto que avanza en x desde 0) y una línea con la duración en ms de cada fotograma. Para cada fotograma: suma su duración a un acumulador (como máximo 250 ms por fotograma, para que un parón no obligue a simular cientos de pasos), ejecuta pasos de física mientras el acumulador llegue a P (guardando la posición anterior y avanzando V·P/1000) y dibuja interpolando entre la posición anterior y la actual con alfa = acumulador / P. Muestra «Fi dt=D[ (limitado a 250)]: N paso(s), sobran A ms, x=X, se dibuja en Y (alfa Z)» con un decimal en las posiciones y dos en alfa, y al final «Fotogramas: F · Pasos de física: N · Tiempo simulado: T ms».

Código de partida (java)
import java.util.*; public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int paso = Integer.parseInt(sc.nextLine().trim().split(" ")[1]);              // «paso 20» (ms)        double velocidad = Double.parseDouble(sc.nextLine().trim().split(" ")[1]);   // «velocidad 120» (px/s)        String[] deltas = sc.nextLine().trim().split(" ");                            // ms de cada fotograma        final int MAXIMO = 250;                                                       // evita la «espiral de la muerte»        double x = 0, xAnterior = 0;        int acumulado = 0, totalPasos = 0;        // TODO: bucle con paso fijo, acumulador e interpolación    }}

Ejemplo: 60 FPS con tirones

Entrada

paso 20
velocidad 120
16 16 17 16 50 16 300 8

Salida esperada

F1 dt=16: 0 pasos, sobran 16 ms, x=0.0, se dibuja en 0.0 (alfa 0.80)
F2 dt=16: 1 paso, sobran 12 ms, x=2.4, se dibuja en 1.4 (alfa 0.60)
F3 dt=17: 1 paso, sobran 9 ms, x=4.8, se dibuja en 3.5 (alfa 0.45)
F4 dt=16: 1 paso, sobran 5 ms, x=7.2, se dibuja en 5.4 (alfa 0.25)
F5 dt=50: 2 pasos, sobran 15 ms, x=12.0, se dibuja en 11.4 (alfa 0.75)
F6 dt=16: 1 paso, sobran 11 ms, x=14.4, se dibuja en 13.3 (alfa 0.55)
F7 dt=300 (limitado a 250): 13 pasos, sobran 1 ms, x=45.6, se dibuja en 43.3 (alfa 0.05)
F8 dt=8: 0 pasos, sobran 9 ms, x=45.6, se dibuja en 44.3 (alfa 0.45)
Fotogramas: 8 · Pasos de física: 19 · Tiempo simulado: 380 ms

8. Colisiones con rejilla espacial

Difícil · Motores de Juegos · apuntesdam.com/subject/prog-multimedia/topic/motores-juegos

Comprobar todos los objetos contra todos cuesta n·(n−1)/2 comprobaciones: con 1000 balas y enemigos, medio millón por fotograma. Los motores primero descartan los pares lejanos (fase amplia) con estructuras como una rejilla espacial. La primera línea es «celda C» y las siguientes cajas «nombre x y ancho alto» (enteros, y hacia abajo). Calcula los choques por fuerza bruta (AABB, ya dado en chocan) y con la rejilla: cada caja se apunta en todas las celdas que ocupa, de floorDiv(x, C) a floorDiv(x+ancho−1, C) en x e igual en y; solo se comprueban los pares distintos que comparten al menos una celda. Muestra «Celdas ocupadas: K · La más llena: (cx,cy) con M» (la primera creada si hay empate), «Choques: a-b c-d…» (cada par en el orden de entrada, ordenados por la primera caja y luego por la segunda; «ninguno» si no hay), «Comprobaciones: fuerza bruta N · rejilla R (ahorro P %)» (P redondeado; 0 si N es 0) y «Mismo resultado: sí».

Código de partida (java)
import java.util.*; public class Main {    record Caja(String nombre, int x, int y, int ancho, int alto) { }     static boolean chocan(Caja a, Caja b) {        return a.x() < b.x() + b.ancho() && a.x() + a.ancho() > b.x()            && a.y() < b.y() + b.alto() && a.y() + a.alto() > b.y();    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int celda = Integer.parseInt(sc.nextLine().trim().split(" ")[1]);   // «celda 64»        List<Caja> cajas = new ArrayList<>();        while (sc.hasNextLine()) {            String[] p = sc.nextLine().trim().split(" ");            if (p.length < 5) continue;            cajas.add(new Caja(p[0], Integer.parseInt(p[1]), Integer.parseInt(p[2]), Integer.parseInt(p[3]), Integer.parseInt(p[4])));        }        // TODO: fuerza bruta frente a rejilla espacial    }}

Ejemplo: Nivel de plataformas

Entrada

celda 64
jugador 40 300 24 32
suelo 0 330 640 20
moneda1 60 300 12 12
moneda2 400 200 12 12
enemigo 380 196 30 30
bala 600 40 6 6
plataforma 360 226 120 16

Salida esperada

Celdas ocupadas: 16 · La más llena: (6,3) con 3
Choques: jugador-suelo jugador-moneda1 moneda2-enemigo
Comprobaciones: fuerza bruta 21 · rejilla 5 (ahorro 76 %)
Mismo resultado: sí

Ejemplo: Coordenadas negativas

Entrada

celda 50
a -30 -30 40 40
b 0 0 10 10
c 200 200 10 10

Salida esperada

Celdas ocupadas: 5 · La más llena: (0,0) con 2
Choques: a-b
Comprobaciones: fuerza bruta 3 · rejilla 1 (ahorro 67 %)
Mismo resultado: sí

9. El efecto túnel: detección discreta frente a continua

Muy difícil · Motores de Juegos · apuntesdam.com/subject/prog-multimedia/topic/motores-juegos

Si una bala avanza 25 píxeles por fotograma y la pared mide 10, puede estar delante en un fotograma y detrás en el siguiente sin que nunca se detecte el choque (efecto túnel). Por eso los motores ofrecen detección continua (Continuous en el Rigidbody de Unity, CCD en Godot). La entrada tiene «pared x y ancho alto», «fps F» y balas «nombre x y vx vy» (velocidades en px/s, y hacia abajo). Simula 1 segundo. Discreta: en cada fotograma k = 1…F la bala está en (x + vx·k/F, y + vy·k/F) y choca si está estrictamente dentro de la pared. Continua: calcula con el método de las franjas el instante t ∈ [0, 1] en que el segmento recorrido entra en la pared (si la velocidad en un eje es 0, la bala debe estar estrictamente dentro de la franja de ese eje; si no, no hay impacto). Muestra «nombre: discreta → choca en el fotograma K (t=T s) | no detecta nada | continua → impacto en t=T s en (X, Y) por la cara izquierda/derecha/superior/inferior | no hay impacto | empieza dentro de la pared», añadiendo « ¡ATRAVIESA LA PARED!» si la continua ve un impacto (sin empezar dentro) que la discreta no detecta; al final «Efecto túnel: N de M balas». Tiempos con tres decimales y posiciones con uno.

Código de partida (java)
import java.util.*; public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        String[] m = sc.nextLine().trim().split(" ");                     // «pared x y ancho alto»        double px = Double.parseDouble(m[1]), py = Double.parseDouble(m[2]);        double pw = Double.parseDouble(m[3]), ph = Double.parseDouble(m[4]);        int fps = Integer.parseInt(sc.nextLine().trim().split(" ")[1]);   // «fps 60»        int tuneles = 0, balas = 0;        while (sc.hasNextLine()) {            String[] b = sc.nextLine().trim().split(" ");            if (b.length < 5) continue;            String nombre = b[0];            double x0 = Double.parseDouble(b[1]), y0 = Double.parseDouble(b[2]);            double vx = Double.parseDouble(b[3]), vy = Double.parseDouble(b[4]);            balas++;            // TODO: detección discreta frente a continua (barrido)        }        System.out.println("Efecto túnel: " + tuneles + " de " + balas + " balas");    }}

Ejemplo: Pared fina

Entrada

pared 300 0 10 200
fps 60
lenta 5 100 600 0
rapida 0 50 1500 0
vertical 305 -50 0 400
diagonal 100 250 600 -300
falla 0 300 900 0

Salida esperada

lenta: discreta → choca en el fotograma 30 (t=0.500 s) | continua → impacto en t=0.492 s en (300.0, 100.0) por la cara izquierda
rapida: discreta → no detecta nada | continua → impacto en t=0.200 s en (300.0, 50.0) por la cara izquierda ¡ATRAVIESA LA PARED!
vertical: discreta → choca en el fotograma 8 (t=0.133 s) | continua → impacto en t=0.125 s en (305.0, 0.0) por la cara superior
diagonal: discreta → no detecta nada | continua → impacto en t=0.333 s en (300.0, 150.0) por la cara izquierda ¡ATRAVIESA LA PARED!
falla: discreta → no detecta nada | continua → no hay impacto
Efecto túnel: 2 de 5 balas

Ejercicios de Programación Multimedia y Dispositivos Móviles

10. ¿Quién gana al tres en raya?

Fácil · Ejercicios de Programación Multimedia y Dispositivos Móviles · apuntesdam.com/subject/prog-multimedia/topic/ejercicios-pmdm

La entrada son 3 líneas con el tablero (X, O o . para las casillas vacías). Muestra «Gana X», «Gana O», «Empate» (tablero lleno sin ganador) o «Sigue la partida».

Código de partida (java)
import java.util.*; public class Main {     static String resultado(char[][] t) {        // TODO        return "Sigue la partida";    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        char[][] t = new char[3][];        for (int i = 0; i < 3; i++) t[i] = sc.next().toCharArray();        System.out.println(resultado(t));    }}

Ejemplo: Gana X en diagonal

Entrada

X.O
OX.
..X

Salida esperada

Gana X

Ejemplo: Empate

Entrada

XOX
XOO
OXX

Salida esperada

Empate

Ejemplo: Sigue

Entrada

X..
.O.
...

Salida esperada

Sigue la partida

11. Una generación del juego de la vida

Medio · Ejercicios de Programación Multimedia y Dispositivos Móviles · apuntesdam.com/subject/prog-multimedia/topic/ejercicios-pmdm

La primera línea tiene filas y columnas; después, el tablero con # (célula viva) y . (muerta). Calcula la siguiente generación del juego de la vida de Conway y muéstrala: una célula viva con 2 o 3 vecinas vivas sobrevive; una muerta con exactamente 3 vecinas vivas nace; el resto muere o sigue muerta. Los bordes no se conectan.

Código de partida (java)
import java.util.*; public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int f = sc.nextInt(), c = sc.nextInt();        char[][] t = new char[f][];        for (int i = 0; i < f; i++) t[i] = sc.next().toCharArray();        // TODO: calcula y muestra la siguiente generación        for (char[] fila : t) System.out.println(new String(fila));    }}

Ejemplo: Parpadeador

Entrada

5 5
.....
..#..
..#..
..#..
.....

Salida esperada

.....
.....
.###.
.....
.....

Ejemplo: Bloque estable

Entrada

4 4
....
.##.
.##.
....

Salida esperada

....
.##.
.##.
....

12. Puntuación con combos

Medio · Ejercicios de Programación Multimedia y Dispositivos Móviles · apuntesdam.com/subject/prog-multimedia/topic/ejercicios-pmdm

Por la entrada llega la secuencia de eventos de una partida, uno por línea: MONEDA (10 puntos), ENEMIGO (50 puntos), GOLPE (pierdes una vida y el combo vuelve a 0). Cada MONEDA o ENEMIGO seguidos aumentan el combo en 1 y los puntos del evento se multiplican por el multiplicador actual: 1 con combo 0-4, 2 con combo 5-9 y 3 con combo 10 o más (el combo se incrementa antes de calcular). Empiezas con 3 vidas; con 0 vidas la partida termina y se ignoran los eventos siguientes. Muestra «Puntos: N», «Vidas: V» y «Mejor combo: C».

Código de partida (java)
import java.util.*; public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int puntos = 0, vidas = 3, combo = 0, mejor = 0;        while (sc.hasNext()) {            String evento = sc.next();            // TODO        }        System.out.println("Puntos: " + puntos);        System.out.println("Vidas: " + vidas);        System.out.println("Mejor combo: " + mejor);    }}

Ejemplo: Combo hasta x2

Entrada

MONEDA
MONEDA
MONEDA
MONEDA
MONEDA
ENEMIGO

Salida esperada

Puntos: 160
Vidas: 3
Mejor combo: 6

Ejemplo: Golpe reinicia el combo

Entrada

ENEMIGO
GOLPE
MONEDA

Salida esperada

Puntos: 60
Vidas: 2
Mejor combo: 1

13. Camino más corto en un laberinto

Difícil · Ejercicios de Programación Multimedia y Dispositivos Móviles · apuntesdam.com/subject/prog-multimedia/topic/ejercicios-pmdm

La IA de un enemigo necesita saber cuántos pasos hay hasta el jugador. La primera línea tiene filas y columnas; después el mapa con # (muro), . (suelo), E (enemigo) y J (jugador). Moviéndose en 4 direcciones, muestra el número mínimo de pasos de E a J, o «Sin camino» si no se puede llegar.

Código de partida (java)
import java.util.*; public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int f = sc.nextInt(), c = sc.nextInt();        char[][] m = new char[f][];        for (int i = 0; i < f; i++) m[i] = sc.next().toCharArray();        // TODO: BFS desde E hasta J        System.out.println("Sin camino");    }}

Ejemplo: Laberinto sencillo

Entrada

3 5
E..#.
.#...
...#J

Salida esperada

6

Ejemplo: Camino directo

Entrada

1 4
E..J

Salida esperada

3

Ejemplo: Sin camino

Entrada

3 3
E#.
##.
..J

Salida esperada

Sin camino

Ejercicios largos

14. Física de un juego de plataformas: gravedad, saltos y colisiones

Muy difícil · JavaScript · 100 minutos · apuntesdam.com/ejercicios/javascript/fisica-de-un-juego-de-plataformas

Los juegos de plataformas 2D (de Super Mario a Celeste) se mueven con unas pocas reglas que se aplican en cada fotograma: leer los controles, mover al personaje, aplicar la gravedad y corregir su posición si ha atravesado una pared o el suelo. Motores como Unity o Godot traen esto hecho, pero entenderlo es lo que permite ajustar el «tacto» del juego y depurar los fallos de colisiones.

El nivel es un mapa de casillas de 16×16 píxeles: # es suelo o pared, C una moneda, E la meta, P la posición inicial del jugador y . aire. El jugador es una caja de 12×14 píxeles (una AABB, caja alineada con los ejes) y todo se calcula con números enteros, como en las consolas antiguas: así el resultado es exacto y repetible.

El truco clásico de las colisiones es mover y corregir cada eje por separado: primero en horizontal (si choca con una pared, se pega a ella) y después en vertical (si cae sobre el suelo, se queda encima y puede volver a saltar). Hacerlo con los dos ejes a la vez provoca que el personaje se enganche en las esquinas.

Requisitos

  • La entrada es el mapa (filas del mismo ancho con ., #, C, E y un solo P), una línea --- y las acciones separadas por espacios en una o varias líneas: R (derecha), L (izquierda), J (salto), combinaciones como RJ, . (nada) y repeticiones como R*10. Errores: Mapa no válido: falta el separador ---, …: las filas no miden lo mismo, …: carácter desconocido y …: tiene que haber un jugador (P) (se comprueban en ese orden).

  • El jugador empieza en x = columna × 16 + 2 e y = fila × 16 + 2 (la casilla de P, apoyado en su borde inferior), con velocidad vertical 0 y fuera del suelo.

  • En cada fotograma, en este orden: (1) la velocidad horizontal es +2 con R, −2 con L (0 con las dos o ninguna); se suma a x, se limita al mapa (0 a ancho×16−12) y, si la caja toca alguna casilla #, se pega a ella (a su izquierda si iba hacia la derecha, a su derecha si no); (2) si hay J y está en el suelo, la velocidad vertical pasa a −9; después se le suma la gravedad (1) con un máximo de 8, se suma a y, y si toca un #: si bajaba, se coloca encima (y queda en el suelo); si subía, se coloca debajo; en los dos casos la velocidad vertical pasa a 0; (3) las monedas que toque la caja se recogen (una vez cada una), la meta termina el nivel y, si y llega a filas×16, el jugador ha caído.

  • Una casilla «toca» la caja si se solapan: la caja ocupa de x a x+11 y de y a y+13. Fuera del mapa no hay suelo (se puede saltar por encima de la fila superior y caer por debajo de la inferior).

  • Mensajes: Fotograma N: moneda (k/total) al recoger cada moneda; Fotograma N: ¡meta! Nivel superado con k/total monedas (y termina); Fotograma N: el jugador ha caído al vacío con k/total monedas (y termina). Si se acaban las acciones: Fin de las acciones (N fotogramas): posición (x, y), en el suelo|en el aire, k/total monedas.

Formato de la entrada

  • El mapa, una línea --- y las acciones (R, L, J, combinaciones, . y repeticiones con *).

Constantes del juego
ConstanteValor
Tamaño de casilla16 px
Caja del jugador12 × 14 px
Velocidad horizontal2 px por fotograma
Impulso del salto−9 px por fotograma
Gravedad+1 px por fotograma²
Velocidad máxima de caída8 px por fotograma

Ejemplo: Recorrer el nivel saltando

Entrada

.............C......
..................E.
...............####.
.....C...####.......
P..####.............
####################
---
R*20 RJ R*12 . . R*24 RJ R*14 R*34 RJ R*16 R*30

Salida esperada

Fotograma 42: moneda (1/2)
Fotograma 111: moneda (2/2)
Fotograma 146: ¡meta! Nivel superado con 2/2 monedas

Ejemplo: Mapas que no valen

Entrada

.............C......
..................E.
...............####.
.....C...####.......
P..####.............
####################
---
R*20 RJ R*12 . . R*24 RJ R*14 R*28 RJ R*16 R*30

Salida esperada

Fotograma 42: moneda (1/2)
Fotograma 106: moneda (2/2)
Fin de las acciones (149 fotogramas): posición (284, 66), en el suelo, 2/2 monedas
Código de partida (javascript)
const lineas = require("fs").readFileSync(0, "utf8").split("\n"); const T = 16;                  // tamaño de casilla en píxelesconst ANCHO = 12, ALTO = 14;   // caja del jugadorconst VEL = 2, SALTO = -9, GRAVEDAD = 1, CAIDA_MAX = 8; function main() {  // TODO: lee y valida el mapa, lee las acciones y simula fotograma a fotograma} main();

15. Inteligencia artificial de un enemigo con una máquina de estados

Difícil · JavaScript · 80 minutos · apuntesdam.com/ejercicios/javascript/ia-enemiga-con-maquina-de-estados

Los enemigos de la mayoría de los videojuegos no piensan: siguen una máquina de estados. En cada momento están en un estado (patrullar, perseguir, atacar…) que decide qué hacen, y unas reglas de transición deciden cuándo cambian a otro. Es fácil de programar, de depurar y de ajustar, y por eso motores como Unity (con su Animator) y Godot la usan a todas horas.

Este guardia vigila un pasillo en un mapa de casillas. Patrulla entre dos puntos; si ve al jugador (a 5 casillas o menos y sin que el jugador vaya en sigilo) lo persigue; si lo tiene al lado, ataca; si lo pierde de vista, va al último sitio donde lo vio y espera un poco antes de volver a patrullar. Y si el jugador le baja la vida por debajo de 30, huye hasta poder recuperarse.

La simulación es por turnos: cada línea de la entrada dice dónde está el jugador en ese turno y si ha golpeado al guardia. El programa escribe cada cambio de estado con su motivo, que es exactamente el registro que se activa en un juego para depurar la IA.

Requisitos

  • La primera línea es PATRULLA x1,y1 x2,y2 (si no, La primera línea debe ser PATRULLA x1,y1 x2,y2 y termina). El guardia empieza en el primer punto, en estado PATRULLA, con 100 de vida. Cada línea siguiente es un turno: x,y del jugador y, opcionalmente, SIGILO y GOLPE n. Las líneas vacías no son turnos; una línea con otra forma escribe t=N: línea no válida (y cuenta como turno).

  • En cada turno: primero se resta el golpe (mínimo 0; si la vida llega a 0, t=N: el enemigo cae derrotado y la simulación termina). Después se calcula la distancia Manhattan al jugador: el guardia lo ve si es 5 o menos y no va en sigilo, y entonces recuerda su posición.

  • Transiciones, se aplica solo la primera que se cumpla: cualquier estado salvo HUIDA → HUIDA si la vida es 30 o menos (vida V); PATRULLA → PERSECUCION si lo ve (te ve a distancia D); PERSECUCION → ATAQUE si la distancia es 1 o menos (a tu lado); PERSECUCION → BUSCAR si la distancia es mayor que 8 (te pierde a distancia D); ATAQUE → PERSECUCION si la distancia es mayor que 1 (te alejas a distancia D); BUSCAR → PERSECUCION si lo ve (te vuelve a ver a distancia D); HUIDA → PATRULLA si la vida es 60 o más (recuperado con vida V). Cada cambio escribe t=N: ANTES → DESPUÉS (motivo).

  • Acción del estado (ya cambiado): PATRULLA, un paso hacia el punto de destino (al llegar a uno, el destino pasa a ser el otro); PERSECUCION, un paso hacia el jugador; ATAQUE, 10 de daño al jugador; BUSCAR, un paso hacia la última posición vista y, una vez allí, espera: al cuarto turno de espera escribe t=N: BUSCAR → PATRULLA (no te encuentra) y vuelve a patrullar; HUIDA, un paso alejándose del jugador si está a 8 o menos, o recupera 10 de vida (hasta 100) si está más lejos.

  • Un paso mueve una casilla en el eje en el que queda más distancia (en horizontal si empatan); alejarse es lo mismo en sentido contrario. Al final: Posición final: (x, y) · vida V · daño causado D y Turnos por estado: PATRULLA n, … con los estados que han tenido algún turno, en el orden PATRULLA, PERSECUCION, ATAQUE, BUSCAR, HUIDA. Cada turno cuenta para el estado en el que termina (el turno en que cae derrotado, para el estado que tenía).

Formato de la entrada

  • Línea 1: PATRULLA x1,y1 x2,y2. Resto: un turno por línea, x,y [SIGILO] [GOLPE n].

Estados y transiciones
EstadoQué haceSale hacia
PATRULLAva y vuelve entre los dos puntosPERSECUCION (te ve) · HUIDA (vida ≤ 30)
PERSECUCIONun paso hacia tiATAQUE (distancia ≤ 1) · BUSCAR (distancia > 8) · HUIDA
ATAQUE10 de daño por turnoPERSECUCION (distancia > 1) · HUIDA
BUSCARva a donde te vio y espera 3 turnosPERSECUCION (te ve) · PATRULLA (no te encuentra) · HUIDA
HUIDAse aleja o, lejos, recupera 10 de vidaPATRULLA (vida ≥ 60)

Ejemplo: Te ve, te persigue, ataca y te pierde

Entrada

PATRULLA 0,0 4,0
9,5
8,4
6,2
4,1
3,2
3,2
3,2
3,2
5,3
9,6
12,8
14,9
15,9
15,9
15,9
15,9
15,9
15,9
15,9

Salida esperada

t=4: PATRULLA → PERSECUCION (te ve a distancia 2)
t=7: PERSECUCION → ATAQUE (a tu lado)
t=9: ATAQUE → PERSECUCION (te alejas a distancia 4)
t=10: PERSECUCION → BUSCAR (te pierde a distancia 10)
t=16: BUSCAR → PATRULLA (no te encuentra)
Posición final: (4, 1) · vida 100 · daño causado 20
Turnos por estado: PATRULLA 7, PERSECUCION 4, ATAQUE 2, BUSCAR 6

Ejemplo: Sigilo, golpes y huida

Entrada

PATRULLA 0,0 0,3
2,0 SIGILO
1,0 SIGILO GOLPE 40
1,0 GOLPE 35
2,0
3,0
4,0
5,0
12,0
12,0
12,0
12,0
12,0

Salida esperada

t=3: PATRULLA → HUIDA (vida 25)
t=10: HUIDA → PATRULLA (recuperado con vida 65)
Posición final: (0, 2) · vida 65 · daño causado 0
Turnos por estado: PATRULLA 5, HUIDA 7
Código de partida (javascript)
const lineas = require("fs").readFileSync(0, "utf8").split("\n"); const VISION = 5, PERDER = 8, ESPERA = 3, VIDA_MAX = 100, HUIR = 30, RECUPERADO = 60; const distancia = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y); function main() {  // TODO: lee la ruta de patrulla y, turno a turno, aplica las transiciones y la acción de cada estado} main();