Enunciado.
El programa propuesto constará de un único fichero fuente,
ranas.cpp
, cuya adecuada compilación producirá
el ejecutable ranas.exe
. Se trata de simular mediante
un programa que realice llamadas a la API de WIN32
la vida de unas ranas, inspirada en el famoso juego de consola
clásico "Frogger".
Según se va ejecutando el programa,
se ha de ver una imagen parecida a la siguiente:
En la imagen pueden observarse las ranas, representadas por una
"m" de color verde. Las ranas nacen de una de cuatro ranas
madre de color verde oscuro y situadas en la parte inferior de
la pantalla. El objetivo de sus vidas es atravesar un río. Para
ello, pueden moverse hacia adelante, hacia la derecha o hacia la
izquierda según su criterio, pero nunca hacia atrás.
Deberán
mantenerse dentro de la pantalla. Dos ranas no pueden ocupar
la misma posición.
Cuando llegan las ranas a la orilla inferior del río, deben
tener cuidado. Sólo pueden saltar en posiciones donde se
encuentran unos troncos flotando sobre su superficie. Si saltan
sobre el agua, se produce un error. Para aumentar la dificultad,
los troncos están a la deriva moviéndose continuamente.
Una rana
puede desaparecer debido a que el tronco sobre el que se encuentra
sale de la pantalla. Eso no constituye un error. Esa rana se ha
"perdido".
En la parte superior de la pantalla aparecen tres contadores.
El primero cuenta las ranas que han nacido. El segundo, las
ranas que han alcanzado la orilla superior del río y se han
puesto a salvo y el tercero, lleva cuenta de aquéllas que se han
perdido porque su tronco desapareció de la pantalla. Al final
de la práctica, el número de ranas nacidas debe coincidir
con el de ranas salvadas más el de ranas perdidas más el de
ranas que permanecen en la pantalla.
El tiempo de la práctica se mide en "tics" de reloj. La
equivalencia entre un tic y el tiempo real es configurable.
Puede ser incluso cero. En ese caso la práctica irá a
la máxima
velocidad que le permita el ordenador donde se esté ejecutando.
ranas.exe
acepta exactamente dos argumentos en la
la línea de órdenes. Si no se introducen argumentos,
se imprimirá
un mensaje con la forma de uso del programa por el canal de
error estándar. El primer argumento será un número
entero
comprendido entre 0 y 1000 que indicará la equivalencia en
ms de tiempo real de un tic de reloj. O dicho de otro modo,
la "lentitud" con que funcionará la práctica. El segundo
argumento es la media de tics de reloj que necesita una
rana madre descansar entre dos partos. Es un número entero
estrictamente mayor que 0.
Para facilitar la tarea, tenéis a vuestra disposición
una biblioteca de funciones de enlazado dinámico
ranas.dll
y un fichero de cabeceras,
ranas.h
.
Gracias a la biblioteca, muchas de las funciones no las tendréis
que programar sino que invocarlas a la DLL, tal como se
explica en la sesión decimoquinta.
La biblioteca creará cuatro hilos adicionales a los vuestros para
su funcionamiento interno, uno para cada rana madre, de los
cuales no tendréis que ocuparos. Una descripción
detallada de las funciones de la biblioteca aparece más abajo
en esta misma página.
La práctica se ejecutará durante 30 segundos.
El programa, desde vuestro punto de vista, se simplifica
bastante. Se ha de llamar a la función InicioRanas
de la DLL. El hilo creará los hilos que necesite para otras
tareas (como mover los troncos del río, por ejemplo) y
dormirá 30 segundos. Invocará a la función
FinRanas
, para hacer que las ranas dejen de
parir. Cuando la
función FinRanas
retorne, el programa se debe
encargar de que el resto de hilos que estén en ejecución
moviendo ranas acaben. Se llamará a continuación a la
función
ComprobarEstadIsticas
(nótese la I mayúscula)
para
que se contrasten las estadísticas que habéis tomado con las
calculadas por la DLL. Ahí acabará el programa. Para esperar la
muerte de los hilos, podEis hacer una pausa sin consumo de CPU
de unos cuantos segundos, aunque se puntuará favorablemente un
esquema mejor. Además de todo esto, se programará
una función que será llamada por la DLL cada vez que
nazca una nueva rana. Dicha
función de rellamada es registrada en la función
InicioRana
de la DLL.
El prototipo de la función de rellamada
se describe aquí:
void f_Criar(int pos)
La DLL llamará a esta función cuando nazca una nueva
rana. El código que programéis para esta
función, aparte
de las necesarias sincronizaciones, consistirá en llamar
a la función PartoRanas
de la DLL, actualizar
las estadísticas y crear un nuevo hilo de ejecución
que
se encargue de mover a la recién nacida. El parámetro
pos
de la función indica la posición
0, 1, 2 ó 3 donde va a nacer la ranita, siendo 0 la
madre de más a la izquierda y, numeradas sucesivamente,
hacia la derecha.
La posición de la rana en la pantalla se determina mediante
dos coordenadas x e y. La x, de izquierda a derecha toma
valores desde 0 a 79. La y crece desde la parte inferior de
la pantalla a la superior y de 0 a 11. Cuando y está entre
0 y 3, se encuentra antes de cruzar el río. El valor 11
corresponde con la orilla opuesta y los siete valores restantes
dan cuenta de las siete filas de troncos flotantes.
Características adicionales que programar
- El programa no debe consumir CPU apreciablemente
en los modos de retardo mayor o igual que 1.
Para comprobar el consumo de CPU, podéis arrancar
el administrador de tareas de Windows NT, mediante
la pulsación de las teclas CTRL+ALT+SUPR.
Observad, no obstante, que en las aulas de informática
puede que esta opción esté deshabilitada.
- IMPORTANTE: Aunque no se indique
explícitamente en el enunciado, parece obvio que
se necesitarán objetos de sincronización
en diferentes partes del programa.
Biblioteca ranas.dll
Con estra práctica se trata de que aprendáis a sincronizar y
comunicar hilos en WIN32. Su objetivo no es la programación.
Es por ello que se os suministra una biblioteca dinámica de
funciones ya programadas para tratar de que no tengáis que
preocuparos por la presentación por pantalla, la gestión de
estructuras de datos (colas, pilas, ...) , etc. También
servirá
para que se detecten de un modo automático errores que se
produzcan en vuestro código. Para que vuestro programa funcione,
necesitáis la biblioteca ranas.dll
y el fichero de cabeceras ranas.h
.
Ficheros necesarios:
Las funciones que la biblioteca exporta para que las uséis se
muestran a continuación. Si una función devuelve un
BOOL
, un valor de TRUE indica que no ha habido
errores y FALSE, lo contrario:
BOOL InicioRanas(int delta_t,
int lTroncos[],int lAguas[],int dirs[],int t_Criar,
TIPO_CRIAR f_Criar)
El hilo principal debe llamar a esta función cuando desee
que la simulación comience. Los argumentos son:
delta_t
: valor del tic de reloj
en milisegundos.
lTroncos
: array de siete enteros
que contiene el valor de la longitud media de
los troncos para cada fila. El índice cero del
array se refiere a la fila superior de troncos.
lAguas
: igual que el parámetro
anterior, pero referido a la longitud media
del espacio entre troncos.
dirs
: lo mismo que los dos parámetros
anteriores, pero en esta ocasión cada elemento
puede valer DERECHA
(0) o
IZQUIERDA
(1), indicando la dirección
en que se moverán los troncos.
t_Criar
: tiempo de reposo entre
dos partos, expresado en tics.
f_Criar
: puntero a la función de
rellamada que será invocada cada vez que una
rana madre deba reproducirse. El prototipo de
la función tiene que ser de modo que devuelva
void
y reciba un argumento entero,
la posición de la rana madre que debe parir.
void Pausa(void)
Hace una pausa, sin consumo de CPU, equivalente a un tic.
void PrintMsg(char *temp)
Imprime el mensaje temp
en la línea de
errores de la aplicación. Sirve para depurar.
BOOL PartoRanas(int i)
Genera una nueva rana delante de la rana madre número i.
La rana aparecerá en las coordenadas (15+16i,0).
BOOL PuedoSaltar(int x, int y, int dir)
La función devuelve si una rana situada en la
posición
(x,y) cumple todas las condiciones para poder avanzar
en la dirección dir
. Los valores posibles
para dir
son los mismos que para
AvanceRana
.
BOOL AvanceRanaIni(int x, int y)
Inicio del avance de una rana situada en (x,y).
BOOL AvanceRana(int *x, int *y, int dir)
Avance efectivo de la rana situada en (x,y).
dir
puede valer DERECHA
(0),
IZQUIERDA
(1) o ARRIBA
(2).
Las variables x
e y
son
modificadas por la función y reflejan la posición de
la rana después del avance.
BOOL AvanceRanaFin(int x, int y)
Fin del avance de una rana situada ya en su posición
de destino (x,y).
BOOL AvanceTroncos(int i)
Avanza una posición la fila de troncos número
i
. Las ranas que hubiera sobre la fila
se mueven con ella.
BOOL FinRanas(void)
Hace que las ranas madres dejen de parir más ranitas.
BOOL ComprobarEstadIsticas(LONG lRNacidas,
LONG lRSalvadas, LONG lRPerdidas)
Se debe llamar a esta función después de haber llamado
a FinRanas
y cuando hayáis acabado con todos
los hilos que mueven las ranas. Se le ha de pasar el número
de ranas nacidas, salvadas y perdidas que vuestro programa
ha contado para ver si las cuentas coinciden con las
que ha realizado la DLL.
Notas acerca de las funciones de la DLL:
- Se puede establecer la longitud media de troncos y
su separación de todas las filas salvo la del medio.
Independientemente del valor espedificado, esta fila
presenta una sucesión de troncos de tamaño dos separados
también una distancia de dos caracteres.
- La secuencia para que una rana se mueva consiste en
llamar primero a
AvanceRanaIni
, luego a
AvanceRana
, ambas con la posición donde
se encuentra la rana. Al retornar AvanceRana, en las
variables de posición ya se encontrará la nueva
posición
de la rana. Hay que llamar a la función Pausa
y, finalmente, a AvanceRanaFin
, con la
nueva posición de la rana.
Sincronización interna de la DLL
La sincronización interna de la DLL está basada en un
mutex.
El esquema de sincronización interna de las funciones que la
tienen es el siguiente:
Mutex Mu;
Mu=1;
[...]
PrintMsg
========
Wait(Mu);
Imprimir mensaje
ReleaseMutex(Mu);
PartoRanas
==========
Comprobación de parámetros
Wait(Mu); Animación; ReleaseMutex(Mu); Pausa(); [[ varias veces ]]
Wait(Mu);
si la posición de parto está ocupada
poner error; ReleaseMutex(Mu); return FALSE;
fin_si
Pintar la nueva rana;
Calcular estadísticas;
ReleaseMutex(Mu);
return TRUE;
AvanceRanaIni
=============
Comprobación de parámetros
Wait(Mu);
si no hay rana en esa posición
poner error; ReleaseMutex(Mu); return FALSE;
fin_si
Actualizar el dibujo de la ranita;
ReleaseMutex(Mu);
return TRUE;
AvanceRana
==========
Comprobación de parámetros
Wait(Mu);
si hay rana en el destino
poner error; ReleaseMutex(Mu); return FALSE;
fin_si
si no hay rana en el origen
poner error; ReleaseMutex(Mu); return FALSE;
fin_si
Borrar la rana del punto de origen
si hemos caído al agua
poner error; ReleaseMutex(Mu); return FALSE;
fin_si
Dibujar a la rana en la nueva posición
ReleaseMutex(Mu);
Actualizar las variables de posición de la rana
return TRUE;
AvanceRanaFin
=============
Comprobación de parámetros
Wait(Mu);
si no hay rana en esa posición
poner error; ReleaseMutex(Mu); return FALSE;
fin_si
Actualizar el dibujo de la ranita;
ReleaseMutex(Mu);
si la posición vertical de la rana es 11
Wait(Mu);
Borrar la rana
Actualizar estadísticas
ReleaseMutex(Mu);
fin_si
return TRUE;
AvanceTroncos
=============
Comprobación de parámetros
Wait(Mu);
Mover troncos
ReleaseMutex(Mu);
return TRUE;