Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Pract 2

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 27

Prctica 2. Programacin de videojuegos usando monitores con Python y Pygame. Programacin Concurrente.

Dpto. Lenguajes y Sistemas Informticos.

1. Introduccin a las hebras


En programacin secuencial se realizan programas con un nico ujo o hebra de control. En un programa de estas caractersticas el control siempre se encuentra en el propio programa, en la ejecucin de llamadas a funciones, o en el ncleo del sistema operativo. Los procesos tradicionales de un sistema operativo (p.e. UNIX) tienen una nica hebra de control denida en su espacio de direcciones. Por otra parte, cuando un proceso crea otro proceso, el proceso padre e hijo pueden compartir algunas de sus variables y otros datos o tener espacios de direcciones completamente independientes. Los procesos que comparten completamente el espacio de direcciones con su proceso padre se denominan procesos ligeros ya que tal comparticin reduce substancialmente el tiempo necesario para crear un proceso hijo o para cambiar el contexto entre procesos. Un sistema operativo construido en base a un ncleo que proporcione procesos ligeros y cuyos mecanismos de intercomunicacin entre procesos asuman dicho esquema es la base ms adecuada para desarrollar software de estaciones de trabajo. El cdigo que sirve para administrar los recursos de dicho sistema (p.e. cheros, E/S, CPUs virtuales, etc.) se suele ejecutar en un espacio comn de direcciones que pertenece al ncleo. Como es sabido, en el ncleo no existen procesos independientes, sin embargo, los servicios aludidos suelen ser encapsulados independientemente y comparten el mismo espacio de direcciones. Los sistemas operativos modernos incluyen el concepto de procesos ligeros para realizar los servicios que proporcionan. Tales procesos tienen acceso a las tablas y estructuras de datos del sistema y sin contar con ellos muchos de los servicios de los sistemas no podran ser implementados. El concepto de multiprogramacin con diferentes hebras de control se deriva de cualquier programa que utilice dos o ms procesos concurrentes que compartan datos en un espacio comn de direcciones. Un proceso servidor, en un SGBD distribuido, que cree una copia de s mismo para servir cada peticin de un cliente diferente es un ejemplo de sistema con mltiples hebras de control. La diferencia entre programar con mltiples hebras y mltiples procesos es doble: independencia y comunicacin. En un programa con mltiples procesos, la separacin entre los procesos es implcita, sin embargo la comunicacin entre los procesos necesita de una mayor atencin del programador. En uno con mltiples hebras, la comunicacin entre las hebras es fcil 1

Prctica 2. Programacin Concurrente.

ya que se realiza a travs de memoria comn, sin embargo, garantizar la independencia de las hebras, mediante los mecanismos de sincronizacin apropiados, resulta ms complicado.

1.1. Creando hebras en Python


La forma de crear hebras en Python es extendiendo la clase Thread.

1.1.1. Heredando de la clase Thread.


Seguimos los siguientes pasos: 1. Creamos una clase que extienda a la clase Thread y redena el mtodo pblico run. El mtodo run es el cdigo que la hebra va a ejecutar cuando sea lanzada. El constructor de la clase (mtodo A1) tambin se redene, permitiendo as asignar un nombre a la hebra (cuando sta sea inicializada):

# -*- coding: iso-8859-15 -*import threading # Variable cerrojo cerrojo = threading.Lock() # Clase A1 class A1 (threading.Thread): # Constructor def __init__(self, nombre, siesta, args): Thread.__init__(self) self.setName(nombre) self._siesta = siesta # Metodo run def run (self): print 'Hola, soy %s' % this.getName() if self._siesta > 0: cerrojo.acquire() else: print 'Me fastidiaron la siesta!'
2. En otro lugar creamos una instancia de la clase denida. De esta forma se crea un objeto hebra (una instancia de una subclase de Thread). 3. Llamamos al mtodo start para la instancia creada. Esta llamada hace que el mtodo run se ejecute como una nueva hebra.

# -*- coding: iso-8859-15 -*import threading

Prctica 2. Programacin Concurrente.

# Variable cerrojo cerrojo = threading.Lock() # Clase A1 class A1 (threading.Thread): # Constructor def __init__(self, nombre, siesta, args): threading.Thread.__init__(self) self.setName(nombre) self._siesta = siesta # Metodo run def run (self): print 'Hola, soy %s' % self.getName() if self._siesta > 0: cerrojo.acquire() else: print 'Me fastidiaron la siesta!' a1 = A1("Hebra_01", 0, []) a1.start()
Normalmente podemos ordenar un poco el cdigo para lanzar varias instancias de la hebra:

# -*- coding: iso-8859-15 -*import threading # Clase A1 class A1 (threading.Thread): # Constructor def __init__(self, nombre, siesta, cerrojo, args): threading.Thread.__init__(self) self.setName(nombre) self._siesta = siesta self._cerrojo = cerrojo # Metodo run def run (self): print 'Hola, soy %s' % self.getName() if self._siesta > 0: self._cerrojo.acquire() else: print 'Me fastidiaron la siesta!'

Prctica 2. Programacin Concurrente.

# Funcion main del programa if __name__== "__main__": # Variable cerrojo cerrojo = threading.Lock() lista = [] # Que pasa si en lugar de 0 pasamos un 1? for i in range(100): lista.append(A1("Hebra_%.2d" % i, 0, cerrojo, [])) for a in lista: a.start()

1.1.2. Herencia mltiple y hebras.


El problema de la forma anterior es que no permite que la nueva clase herede de otras clases diferentes de Thread. Python permite herencia mltiple, sin embargo hay veces en los que se puede producir conictos. Al igual que en C++, Python los resuelve aunque de forma distinta. Un problema de utilizar herencia mltiple es que en el siguiente cdigo:

clase A metodo() Escribir("soy A") clase B metodo() Escribir("soy B") clase C, hereda de A y B c es instancia de C c.metodo()
qu debe hacer c.metodo()? C++ lo resuelve en tiempo de compilacin, pero Python es interpretado, por lo que no puede resolverlo en tiempo de compilacin. Lo que hace es bastante simple: Lleva a cabo una bsqueda en profundidad Si tenemos esta jerarqua:

Prctica 2. Programacin Concurrente.

Buscar en la clase E Si no est, buscar en C Si no est, buscar en B Si no est, buscar en A Si no est, buscar en D Si no est, buscar en B Si no est, buscar en A Si no est, enviar una excepcin

Podemos hacer que la clase anterior herede adicionalmente de otra clase:

# Clase B1 class B1: def __init__(self): print 'Hola' # Clase A1 class A1 (threading.Thread, B1): # Constructor def __init__(self, nombre, siesta, cerrojo, args): # Llamada al primer constructor B1.__init__(self) # Llamada al segundo constructor threading.Thread.__init__(self) self.setName(nombre) self._siesta = siesta self._cerrojo = cerrojo # Metodo run def run (self): print 'Hola, soy %s' % self.getName() if self._siesta > 0: self._cerrojo.acquire() else: print 'Me fastidiaron la siesta!'
Normalmente, si los mtodos de B1 son pblicos, podemos implementar un objeto de la clase B1 y pasrselo al constructor, de forma que esta clase pueda acceder a los mtodos de la clase B1 a travs del objeto creado. En la mayora de los casos, suele ser una solucin ms elegante que la implementacin basada en herencia mltiple.

1.2. Clases y mtodos asociados a una hebra


Una hebra en Python es una clase que hereda de Thread, Thread es la clase de alto nivel de Python para manejar hebras. Existen, sin embargo, varias clases adicionales que sirven para

Prctica 2. Programacin Concurrente.

realizar distintas operaciones sobre hebras:

Thread La clase Thread permite la ejecucin concurrente de trozos de cdigo y su manipulacin.

Existen dos formas de utilizarlas, el primero es pasar como argumento una funcin, la cual se ejecutar simtricamente con respecto a otras hebras del proceso. La otra, ms comn, es extender la clase, y en concreto, el mtodo run (tal y como se ha visto en el apartado anterior). Sus mtodos son los siguientes:

start() Lanza la hebra. Internamente llama al mtodo run. run() Indica el cdigo que se ejecutar cuando se lance la hebra. join() Espera a que la hebra termine, salvo cuando hay algn temporizador (timeout). getName() Devuelve el nombre de la hebra. setName() Cambia el nombre de la hebra. isAlive() Devuelve si la hebra est viva (ejecutndose) o no. isDaemon() Devuelve el ag de demonio. setDaemon() Activa el ag de demonio de la hebra. Un programa en Python puede
salir solamente cuando no hay hebras de demonio en su interior.

Lock Esta clase implementa un mtodo sencillo de cerrojo (se puede ver un ejemplo en el cdigo
anterior). Solamente tiene dos mtodos: Tiene un parmetro opcional.

acquire( blocking = 1 ) Adquiere un cerrojo, bloqueando el sistema o desbloquendolo. release() Abre un cerrojo. RLock Esta clase implementa un cerrojo reentrante. Es igual que el sistema de la clase Lock,
salvo que este cerrojo tiene incorporado un mtodo para controlar algoritmos recursivos. Solamente tiene dos mtodos:

acquire( blocking = 1 ) Adquiere un cerrojo, bloqueando el sistema o desbloquendolo.


Tiene un parmetro opcional.

release() Abre un cerrojo. Condition Una variable condicin est asociada siempre con algn tipo de cerrojo (el cual se
le suele pasar al constructor de la clase, siendo de tipo Lock o RLock). Tiene los siguientes mtodos:

acquire( *args ) Adquiere un cerrojo. release() Abre un cerrojo. wait( timeout ) Espera hasta que haya alguna noticacin o se pase el tiempo indicado
en la funcin. Este mtodo abre el cerrojo y espera un notify() o un notifyAll(). debe ser llamado slo cuando esta hebra haya adquirido el cierre.

notify() Despierta una hebra que est esperando esta condicin (si la hay). Este mtodo notifyAll() Despierta a todas las hebras que estn esperando esta condicin. Semaphore Implementa un semforo, tiene dos mtodos: acquire y release.

Prctica 2. Programacin Concurrente.

Event Implementa un sistema sencillo de eventos entre hebras. Los mtodos que tiene asociados
son los siguientes:

isSet() Devuelve true si el ag interno es verdadero. set() Cambia a true el ag interno. clear() Cambia a false el ag interno. wait() Espera hasta que el ag interno sea true. Timer Esta clase es una hebra de ejemplo que extiende a la clase Thread. Ejecuta una funcin
despus de que pase un tiempo:

def hola(): print "hola mundo!" t = Timer(30.0, hola) t.start() # tras 30 segundos aparecer\'a en pantalla: "hola mundo!"
Para utilizar cualquiera de estas clases ha de importarse el paquete threading. Este paquete es tambin un objeto con unos mtodos asociados. Por ejemplo, podemos conocer el nmero de hebras que se estn ejecutando en este momento escribiendo:

threading.activeCount()
La documentacin detallada de todos estos mtodos se encuentra en: http://docs.python.org/lib/module-threading.html

2. Introduccin a Pygame y la programacin de juegos 2D


Una de las razones para comenzar a desarrollar videojuegos 2D, es que los conceptos involucrados son muchos ms simples y fciles de asimilar que en juegos 3D. Otra ventaja es que obtendremos resultados ms rpidos, a diferencia del desarrollo 3D, que involucra conocer tpicos ms avanzados de matemticas y de programacin. Antes de comentar la librera de juegos Pygame, comentaremos algunos aspectos y la terminologa bsica que nos har comprender de mejor forma los conceptos grcos involucrados en un juego.

2.1. Sprites y animacin de Sprites


Un Sprite es cualquier objeto que aparece en nuestro videojuego, aunque normalmente suele relacionarse a todo personaje o animacin dentro del mismo. Normalmente tienen una serie de atributos, entre los ms bsicos tenemos las imgenes que lo componen y su posicin.

Prctica 2. Programacin Concurrente.

Podemos tener dos tipos de Sprite en un juego: 1. Dinmicos: se referirn a todos los personajes y animaciones. 2. Estticos: se referirn a todos los elementos del juego que no sean el fondo de la pantalla y que no se muevan a lo largo del juego. Las imgenes de los Sprites suelen tener una parte que es transparente, por ello, se marca mediante el canal llamado: Canal Alfa, un buer que nos indica qu partes de la imagen son transparentes y cuales opacas. Normalmente los programas de dibujo y tratamiento de imagen suelen asociar el canal alfa mostrando un fondo de cuadros y la imagen sobre este fondo, indicando que la imagen en esa parte es transparente o translcida. La otra forma de hacer esto mismo, es utilizar un color key, o color base, el cual se utiliza para marcar la transparencia, para ello, basta con que usemos un determinado color en RGB para marcar la transparencia de todas las imgenes. Por tanto, estos Sprites suelen tener fondos chillones ya que es poco probable que se encuentre ese color en la imagen.

2.2. Iniciacin a las Pygame


Las Pygame es una librera que manipula por debajo una serie de libreras adicionales de imgenes, sonido, msica, vdeo, etc., de tal forma que crear juegos con esta librera es sencillo (podramos llamarla un motor de juegos 2D). Esta librera permite realizar mltiples tareas que nos simplicarn enormemente la creacin de cualquier juego o aplicacin grca animada. Lo primero que debemos hacer en Python es, como siempre inicializar las libreras:

import os, sys import pygame from pygame.locals import * if not pygame.font: print 'Advertencia!, estilos y letras desactivadas.' if not pygame.mixer: print 'Advertencia!, sonidos desactivados.'
Podemos inicializar la librera y crear una ventana en la pantalla simplemente aadiendo esto en nuestro programa principal:

Prctica 2. Programacin Concurrente.

pygame.init() screen = pygame.display.set_mode((468, 60)) # Resoluci\'on de la ventana (tamagno) pygame.display.set_caption('Nombre de la ventana') pygame.mouse.set_visible(0) # Oculta el raton (si se comenta, aparece el raton)

2.3. Carga y visualizacin del fondo y ttulos


Los fondos y ttulos normalmente no tienen asociado un canal alfa (es decir, no tienen partes transparentes puesto que detrs no hay nada).

1 Normalmente debemos usar una funcin diferente de carga para el fondo que para cualquier Sprite, ya que en este caso no tendremos un color base ni un canal alfa para transparencia. Cargar una imagen en Pygame es sumamente sencillo:

def carga_fondo(name, colorkey=None): fullname = os.path.join('data', name) try: image = pygame.image.load(fullname) # Intenta cargar la imagen except pygame.error, message: # Si no puede muestra un mensaje print 'No se puede cargar la imagen:', name raise SystemExit, message image = image.convert() # Convierte la imagen al formato propio para visualizar return image, image.get_rect() # Devuelve la imagen y su tamagno

2.4. Carga y visualizacin de un Sprite esttico


Como hemos comentado previamente, un Sprite debe cambiar el fondo para que sea transparente. En Pygame, esto es tan sencillo como modicar un ag:

def carga_imagen(name, colorkey=None): fullname = os.path.join('data', name) try: image = pygame.image.load(fullname) # Intenta cargar la imagen

Prctica 2. Programacin Concurrente.

10

except pygame.error, message: # Si no puede muestra un mensaje print 'No se puede cargar la imagen:', name raise SystemExit, message image = image.convert() # Convierte la imagen al formato propio para visualizar if colorkey is not None: # Si se le dice que hay un color base if colorkey is -1: # Si es -1, lo coge a partir del primer pixel colorkey = image.get_at((0,0)) image.set_colorkey(colorkey, RLEACCEL) # Activa el flag con el color base return image, image.get_rect() # Devuelve la imagen y su tama\~no
Podemos utilizar esta misma funcin para la carga de fondos, basta con pasar solo un parmetro a la funcin.

2.5. Carga y visualizacin de un Sprite dinmico: Animacin


Para crear un Sprite dinmico, debemos comprender primero cmo se anima un juego 2D. Para animar un juego 2D, cada Sprite tiene asociado un array de imgenes, las cuales se muestran cada cierto tiempo para dar sensacin de animacin. Cada una de estas imgenes se suele denominar frame o fotograma. De tal forma que la abreviacin fps se referir a los fotogramas por segundo que se tienen. Cuantos ms fps, ms uida ser la animacin, pero tambin ocupar ms espacio en disco y en memoria (ya que el array ser mayor). Normalmente, todas las animaciones se pintan sobre una misma imagen llamada: hoja de Sprites (o Sprite sheet ). Para coger cada una de las imgenes que formarn la animacin podemos utilizar la siguiente funcin:

# Divide la imagen en subimagenes: def divide_imagen(image, rect): subimage = image.subsurface(rect) return subimage, rect # Devuelve la imagen y su tamagno
Con esto no se crea una nueva imagen, si no que se referencia sobre la anterior, siendo ms eciente (ya que no ocupamos tanta memoria) y ms ptimo. Para girar una imagen podemos usar este cdigo:

# Damos la vuelta a la imagen para que mire al otro lado img = pygame.transform.flip(imagen[0], 1, 0)
Donde imagen[0] es la imagen obtenida cargndola previamente de un chero con la funcin anterior. Para que en el juego, todo vaya a la velocidad que debe ir, y no dependa de la mquina en la que lo ejecutemos (si tenemos un ordenador muy potente, es capaz de ejecutar el cdigo ms

Prctica 2. Programacin Concurrente.

11

rpido, y por tanto de dibujar ms fotogramas por segundo), lo que se suele hacer es incorporar un reloj en el sistema, de forma que cada Sprite sea capaz de dibujarse en una posicin concreta cuando le toca. En Pygame, tenemos un reloj que hace esto por nosotros:

clock = pygame.time.Clock() # Definimos un reloj while 1: clock.tick(60) # Este bucle no ira m\'as rapido de 60 fps

Estas hojas suelen montarse para no estar trabajando con cientos (o a veces miles) de imgenes. De hecho, las animaciones de los personajes suelen venir dadas de un lado nicamente, debido a que se pueden invertir (la operacin se conoce como ip ) respecto al eje que se desee. Normalmente, tambin se suele soportar las rotaciones con un ngulo arbitrario, por lo que si la imagen tiene una vista superior, una nica imagen nos sirve para todos los ngulos en el juego.

2.6. Mezclando todo: Blitting


El blitting es una operacin para transferir un bloque de bytes de un sector de la memoria a otra. Este trmino viene del trmino ingls blit (Block Image Transfer, Transferencia de Imagen por Bloque) y es una forma de dibujar Sprites con o sin transparencias sobre un fondo (que normalmente es un buer que representa a la propia pantalla). Esta tcnica puede ser acelerada por hardware, lo cual ayuda a incrementar el rendimiento, de hecho es una de las operaciones ms crticas en cualquier juego 2D. Gracias al blitting podemos montar una escena en un videojuego. En una operacin de blitting, podemos especicar en qu posicin de la imagen de destino queremos copiar la imagen origen, incluso qu parte de la supercie origen queremos copiar. De esta forma podemos colocar en cualquier posicin, unas imgenes dentro de otras. Normalmente, y para que todo vaya rpido, se monta la escena primero mediante esta operacin, y se coge la imagen resultante y se manda a la pantalla. De forma que en pantalla solo dibujamos una nica vez por fotograma del videojuego (lo cual es bastante ms rpido que pintar varias veces en pantalla por cada fotograma). A esta tcnica se le llama double buering (o doble buer). En Pygame, tenemos un buer que es el buer de pantalla, donde se dibuja toda la escena, y posteriormente se manda a la pantalla este buer utilizando esta tcnica, mediante una funcin:

background = carga_imagen('escenario.jpg')

Prctica 2. Programacin Concurrente.

12

spritesheets1 = carga_imagen('spritesheet.png', -1) # A\~nadimos una animacion: anim0 = [] anim0.append(divide_imagen(spritesheets1[0], pygame.Rect(11, 5, 45-11, 93-5))) # Dibujamos en el buffer de pantalla: screen.blit(background[0], (0, 0)) # Damos la vuelta a la imagen para que mire al otro lado img = pygame.transform.flip(anim0[0][0], 1, 0) # Unimos la imagen a la pantalla, en la coordenada 10, 75 screen.blit(img, (10, 75)) # Volcamos el buffer de pantalla (screen) en la pantalla pygame.display.flip()

Para facilitar la carga de las animaciones se puede utilizar bucles siempre y cuando cada animacin del personaje tenga el mismo tamao y estn en posiciones equidistantes en la hoja de Sprites. En otro caso hay que ajustar a mano cada uno de las animaciones o hacer una utilidad que nos devuelva la posicin y el tamao de cada una de estas subimgenes.

2.7. Tratamiento de los Sprites en Pygame


Para tratar todos los Sprites, Pygame dispone de una clase especial (pygame.sprite.Sprite), que provee de unos mtodos para el tratamiento de Sprites. En Pygame (y en general en cualquier librera para juegos) cada tipo de personaje es una clase. Con tipo de personaje nos referimos al conjunto de personajes que acta de una misma forma. Por ejemplo, en un juego estilo Prince of Persia, en el que tenemos un personaje principal que lucha contra unos villanos, tendramos dos clases: Jugador y Villano.

Prctica 2. Programacin Concurrente.

13

Da igual que haya varios villanos. Tendremos una nica clase para ellos, ya que comparten grcos y el modo de actuar (su comportamiento). Por ejemplo, si tenemos un Sprite esttico para dibujar piedras y rboles esta podra ser su clase:

class Objeto(pygame.sprite.Sprite): """Este es un sprite del escenario""" def __init__(self, image): pygame.sprite.Sprite.__init__(self) # Carga la imagen y tambien su posicion y tamagno self.image, self.rect = load_image(image, -1) def update(self): "Actualiza el Sprite"

Incluso cuando el Sprite no haga nada, conviene extender el mtodo update. Cada Sprite puede pertenecer a un grupo, lo que facilita una divisin conceptual. Por ejemplo, podemos tener una grupo de Sprites que sean Escenario, e introducir tanto Sprites estticos como dinmicos en l. Tambin podramos tener otro grupo que fuera Enemigos, y otro llamado Personaje. Un mismo Sprite puede estar en varios grupos. Los mtodos de un Sprite, por tanto, son los siguientes:

update(*args) Controla el comportamiento del Sprite add(*groups) Aade el Sprite a un grupo remove(*groups) Elimina el Sprite de un grupo kill() Elimina el Sprite de todos los grupos alive() Devuelve verdadero si el Sprite est en algn grupo groups() Devuelve la lista de grupos donde est el Sprite
Los mtodos de la clase Group son los siguientes:

sprites() Una lista de todos los Sprites de este grupo copy() Duplica el grupo add(*sprites) Aade Sprites del grupo remove(*sprites) Elimina Sprites del grupo has(*sprites) Comprueba que existan todos los Sprites en el grupo draw(surface) Dibuja todos los Sprites

Prctica 2. Programacin Concurrente.

14

update(*args) Llama al mtodo update de todos los Sprites clear(dest,bg) Dibuja un fondo sobre los Sprites (los borra) empty() Elimina todos los Sprites

2.8. Tratamiento de los eventos en Pygame


Todos los juegos tienen por defecto algn procesamiento de eventos. En concreto, hay juegos que necesitan del ratn, del teclado o del joystick en cualquier momento de ejecucin del mismo. En Pygame, la lectura de eventos es muy simple. De forma general, tendremos un bucle para leer todos los eventos en un fotograma dado, y en funcin de ese evento podemos realizar una operacin u otra:

for event in pygame.event.get(): # Se leen los eventos if event.type == QUIT: # Si se pulsa el boton de cerrar ventana se sale exit(1) elif event.type == KEYDOWN and event.key == K_ESCAPE: # Tecla Escape exit(1)
Para un ejemplo completo de una demo de un juego, mirar el chero pg3.py. Toda la documentacin de la librera y algunos tutoriales se pueden encontrar en la siguiente direccin: http://www.pygame.org/docs/ Con la funcin:

fsprites.extrae_animacion(self.image, (806, 131, 840-806, 225-131))


Podemos extraer fcilmente de una tira horizontal todas las animaciones. Los parmetros son: la imagen de origen, el rectngulo que dene el rea donde buscar. El problema principal de las imgenes, es que la posicin del Sprite se da sobre la esquina superior izquierda. Si el personaje tiene una vista lateral y hay animaciones con imgenes ms pequeas que otras, las cuadra sobre esta esquina haciendo que el personaje vuele.

Para evitarlo, se puede situar el personaje sobre la esquina inferior izquierda restando a cada operacin blit el tamao del rectngulo en el eje y, tal y como muestra este cdigo:

def draw(self): """Dibuja el Sprite"""

Prctica 2. Programacin Concurrente.

15

# Si la lista de animaciones es vacia, entonces pon la animacion 0 if self.listanim != []: anim = self.listanim[-1] else: anim = self.esperando # Damos la vuelta a la imagen para que mire al otro lado img = pygame.transform.flip(anim[self.frame][0], self.lado, 0) # Unimos la imagen a la pantalla, restamos en el eje y para que este en el suelo pos = pygame.Rect(self.rect[0], self.rect[1] - anim[self.frame][1][3], self.rect[2], screen.blit(img, pos)
Una vez que queremos introducir cualquier animacin basta con introducirla en la lista de animaciones. Habr animaciones que tengan mxima prioridad (como cuando el personaje muere) y otras que debern nalizarse para comenzar una nueva animacin. Esto es sencillo de ejecutar, ya que tenemos un bucle recorriendo los fotogramas. Si la prioridad es pequea, cuando el fotograma llega al nal se elimina la animacin de la lista y se coge la siguiente animacin. En otro caso, no se espera a nalizar los fotogramas de la animacin sino que se ejecuta directamente la siguiente animacin, esto lo podemos ver en este trozo de cdigo en la funcin update:

def update(self): """Anima el personaje""" # Si la lista de animaciones es vacia, entonces pon la animacion 0 if len(self.listanim) > 0: anim = self.listanim[-1] else: anim = self.esperando # Condiciones de muerte: if self.rect[0] < 10 and anim != self.muriendo: self.listanim = [self.muriendo] self.frame = 0 # Si llega al final arriba, muerte, maxima prioridad if self.suelomuerte < 300 and self.rect[0] > 300 and anim != self.muriendo: self.listanim = [self.muriendo] # Sustituye toda la lista de animaciones self.frame = 0 # Si llega al final abajo, muerte, maxima prioridad if self.suelomuerte >= 300 and self.rect[0] > 150 and anim != self.muriendo: self.listanim = [self.muriendo] # Sustituye toda la lista de animaciones self.frame = 0 self.clock = self.clock + 1

Prctica 2. Programacin Concurrente.

16

# Ve al siguiente frame, a 4 fps if self.clock > 4: self.clock = 0 self.frame = self.frame + 1 # Aumenta en uno el fotograma if anim == self.corriendo: if self.lado == 0: self.rect = self.rect.move(5, 0) # Mueve a dcha else: self.rect = self.rect.move(-5, 0) # Mueve a izq # Si se acabo la animacion, saca la animacion de la lista if self.frame >= len(anim): if anim != self.muriendo: self.frame = 0 if len(self.listanim) > 0: self.listanim.pop() if anim == self.vuelta: self.lado = not self.lado # Cambiamos el lado anim = self.esperando else: self.frame = len(anim) - 1

Ejercicio propuesto:
Aade una nueva animacin al personaje cuando se pulsa la tecla de la echa arriba (K_UP).

3. Introduccin a los monitores


Los monitores derivan de una prctica antigua de programacin de los sistemas operativos, denominada construccin de un monitor monoltico, dichos monitores se ejecutan en un modo privilegiado, frente a otros programas del sistema o de los usuarios. Cuando se ejecuta cdigo de un monitor (se entra en un monitor), se prohben las interrupciones y, por tanto, se asegura la exclusin mutua en el acceso al recurso protegido. Adems, slo cuando se ejecuta el cdigo del monitor se est autorizado para acceder a ciertas reas de memoria, o a ejecutar ciertas instrucciones de E/S. El desarrollo de programas y aplicaciones-software con monitores es considerado como una forma de programacin modular, ya que los monitores son autnticos mdulos, segn la denicin que hace de stos la ingeniera del software y la programacin orientada a objetos. La aportacin ms importante de la programacin modular es la de introducir el principio de ocultacin de detalles no relevantes cuando desarrollamos componentes de una aplicacin en un determinado nivel abstraccin. Si se est desarrollando el cdigo del procedimiento principal de un programa, entonces no tiene sentido el preocuparse de los detalles relativos a cmo va a ser el aspecto nal de la interfaz de usuario.

Prctica 2. Programacin Concurrente.

17

Para poder mantener la coherencia de los datos que estn sujetos a un acceso concurrente, no se puede utilizar, dentro del texto de los procedimientos, ninguna variable declarada fuera del monitor. Lo primero para crear un monitor en Python es crear una clase que contendr el cdigo que gestiona un proceso completo. Supongamos que lo que queremos hacer es un programa que sume de uno en uno hasta llegar a 5.000. Para ello crearemos 5 hebras y la siguiente clase:

# Clase Contador class Contador: """ Clase contadora """ def __init__(self, inicial): self.actual = inicial def inc(self): self.actual = self.actual + 1 def dec(self): self.actual = self.actual - 1 def valor(self): return self.actual
El problema que tenemos con esta clase, es que si desde cualquier hebra llamamos a uno de sus mtodos, no nos aseguramos de que se ejecute nicamente uno de sus mtodos al mismo tiempo. Para asegurarnos, podemos utilizar un objeto Condition.

3.1. Objetos Condition en Python


En Python no existe el concepto de variable condicin. Podramos decir que cada monitor en Python puede usar varias variables condicin que dependan de uno o ms cerrojos. Los monitores de Python implementan la disciplina signal and continue (para ello usan los mtodos acquire y release). Los mtodos wait, notify y notifyAll implementan los mecanismos de espera y noticacin de los monitores Python. Estos mtodos solamente pueden ser llamados por una hebra cuando sta posee el cerrojo del objeto, es decir, desde un bloque entre acquire y release. La invocacin al mtodo wait provoca que la hebra actual se bloquee y sea colocada en un conjunto de espera asociado al objeto monitor. El cerrojo del objeto es liberado para que otras hebras puedan ejecutar mtodos del objeto. Sin embargo, otros cerrojos posedos por la hebra suspendida son retenidos por sta. La invocacin al mtodo notify provoca que, si hay alguna hebra bloqueada en wait, se escoja una cualquiera de forma arbitraria, y se saque del conjunto de wait pasando sta al estado preparado. La hebra que invoc notify seguir ejecutndose dentro del monitor. La hebra sealada debera adquirir el cerrojo del objeto para poder ejecutarse. Esto signicar

Prctica 2. Programacin Concurrente.

18

esperar al menos hasta que la hebra que invoc notify libere el cerrojo, bien por la ejecucin de una llamada a wait, o bien por la salida del monitor. La hebra sealada no tiene prioridad alguna para ejecutarse en el monitor. Puede ocurrir que, antes de que la hebra sealada pueda volver a ejecutarse, otra hebra adquiera el cerrojo del monitor. La invocacin al mtodo notifyAll produce el mismo resultado que que una llamada a notify por cada hebra bloqueada en el conjunto de wait: todas las hebras bloqueadas pasan al estado preparado. La forma de crear la variable condicin es la siguiente:

# Clase A1 class A1 (threading.Thread): """ Clase que hace uso del contador """ def __init__(self, cnt, cond, nm): # Llamada al segundo constructor threading.Thread.__init__(self) self.contador = cnt self.condicion = cond self.nombre = nm def run (self): # Metodo run for i in range (1000): self.contador.inc() v = self.contador.valor() print 'Soy %s' % self.nombre print 'mi contador vale: %d' % v # Funcion main del programa if __name__== "__main__": # Variable cerrojo cerrojo = threading.Lock() condicion = threading.Condition(cerrojo) contador = Contador(0) lista = [] for i in range(5): lista.append(A1(contador, condicion, "Hebra %d" % i)) for a in lista: a.start()

Ejercicio propuesto:
Modica la clase Contador para que no se pueda entrar al cdigo de dos de sus mtodos al mismo tiempo.

Prctica 2. Programacin Concurrente.

19

3.2. Productor consumidor en Python con Pygame


Un problema clsico en concurrencia es el productor consumidor. Podemos ver el problema como un mercader que tiene una serie de sacos y un comprador que va a comprar parte de estos sacos. La mercanca llega cada cierto tiempo, por ello el comprador ha de esperar cuando no haya nada en stock. De igual forma, el vendedor tiene un almacn limitado, por lo que no puede almacenar ms que un nmero reducido de sacos. En general, las variables Condition de Python se pueden usar para crear monitores en cualquier situacin. Por ejemplo, se va a disear una solucin para el problema del productor consumidor, en el que tendremos un productor y un solo consumidor. La forma de implementarlo en Python, viene dado por el siguiente esquema:

# Consume un elemento cv.acquire() while not un_elemento_disponible(): cv.wait() coge_un_elemento_disponible() cv.release() # Produce un elemento cv.acquire() crear_un_elemento_disponible() cv.notify() cv.release()
Hay que tener en cuenta que adems de proteger los mtodos del monitor, hay que proteger tambin los mtodos que acceden a la animacin de los personajes ya que si no podemos ocasionar una inconsistencia en los datos (o que el programa salga debido a cualquier error). Tambin debemos considerar que los personajes van a actuar como monitores, por tanto, es recomendable protegerlos.

Un ejemplo de un personaje completo lo tenemos en el mercader:

class Mercader(pygame.sprite.Sprite): """ Productor """ def __init__(self, x, y, lad):

Prctica 2. Programacin Concurrente.

20

self.image, self.rect = fsprites.carga_imagen('mercante.png', -1) self.cerrojo = threading.Lock() # Ahora necesitamos un cerrojo para que no se llamen self.lado = lad self.listanim = [] self.clock = 0 self.frame = 0 self.esperando = fsprites.extrae_animacion(self.image, (7, 56, 205-7, 103-56)) self.regateando0 = fsprites.extrae_animacion(self.image, (91, 0, 264-91, 50-0)) self.regateando1 = fsprites.extrae_animacion(self.image, (1, 107, 264-1, 152-107)) self.soltando = fsprites.extrae_animacion(self.image, (230, 4, 345-230, 48-4)) self.cogiendo = fsprites.extrae_animacion(self.image, (224, 57, 372-224, 103-57)) # Movemos el mu\~neco en la posicion correcta: self.rect = self.rect.move(x, y) def update(self): """Anima el personaje""" self.cerrojo.acquire() # Si la lista de animaciones es vacia, entonces pon la animacion 0 if len(self.listanim) > 0: anim = self.listanim[-1] else: anim = self.esperando self.clock = self.clock + 1 # Ve al siguiente frame, a 8 fps if self.clock > 8: self.clock = 0 self.frame = self.frame + 1 # Aumenta en uno el fotograma # Si se acabo la animacion, saca la animacion de la lista if self.frame >= len(anim): self.frame = 0 if len(self.listanim) > 0: self.listanim.pop() self.cerrojo.release()

def regatear(self): self.cerrojo.acquire() if len(self.listanim) <= 0 or (self.listanim[0] != self.regateando0 and self.listanim self.listanim.insert(0, self.regateando0) self.listanim.insert(0, self.regateando1) self.listanim.insert(0, self.regateando0) self.listanim.insert(0, self.regateando1)

Prctica 2. Programacin Concurrente.

21

self.cerrojo.release() def coger(self): self.cerrojo.acquire() if len(self.listanim) <= 0 or self.listanim[0] != self.cogiendo: self.listanim.insert(0, self.cogiendo) self.cerrojo.release() def soltar(self): self.cerrojo.acquire() if len(self.listanim) <= 0 or self.listanim[0] != self.soltando: self.listanim.insert(0, self.cogiendo) self.cerrojo.release() def ocupado(self): return len(self.listanim) def draw(self): """Dibuja el Sprite""" self.cerrojo.acquire() # Si la lista de animaciones es vacia, entonces pon la animacion 0 if self.listanim != []: anim = self.listanim[-1] else: anim = self.esperando # Damos la vuelta a la imagen para que mire al otro lado img = pygame.transform.flip(anim[self.frame][0], self.lado, 0)

# Unimos la imagen a la pantalla, restamos en el eje y para que este en el suelo if self.lado == 0: pos = pygame.Rect(self.rect[0], self.rect[1] - anim[self.frame][1][3], self.rect[ else: pos = pygame.Rect(self.rect[0] - anim[self.frame][1][2], self.rect[1] - anim[self screen.blit(img, pos) self.cerrojo.release()
Hay que tener muy en cuenta algunos trozos de cdigo, en especial, aquellos que devuelvan algo:

def ocupado(self): return len(self.listanim)


Ese mtodo no puede protegerse aadiendo las dos lneas previas:

# Esta mal:

Prctica 2. Programacin Concurrente.

22

def ocupado(self): self.cerrojo.acquire() # El cerrojo se adquiere return len(self.listanim) # Sale del metodo self.cerrojo.release() # El cerrojo nunca se libera
Este es un problema de estructuracin de cdigo. En general, siempre que se haga uso de variables o mtodos locales a este mtodo (no a la clase), no hace falta proteger el cdigo. Por tanto podemos reescribir el cdigo anterior para que est correcto:

def ocupado(self): self.cerrojo.acquire() # El cerrojo se adquiere tmp = self.listanim # Nueva variable local self.cerrojo.release() # El cerrojo se libera return len(tmp)
Ahora no hay problema de que se calcule la longitud de la lista de animaciones ya que se calcula sobre la copia local. An as, la variable puede contener un valor no actualizado, ya que se puede haber sacado algn elemento de la lista mientras se calculaba la longitud de la lista. Podemos mejorar el cdigo anterior minimizando el nmero de operaciones del return:

# Esta correcto: def ocupado(self): self.cerrojo.acquire() # El cerrojo se adquiere tmp = len(self.listanim) # Nueva variable local self.cerrojo.release() # El cerrojo se libera return tmp # No se opera, solamente se devuelve la variable

Ejercicio propuesto:
Resolver todos los casos conictivos del problema del productor consumidor.

Prctica 2. Programacin Concurrente.

23

El esquema de la aplicacin se divide en los siguientes pasos: 1. Por defecto, el mercader espera hasta que llegue un consumidor (cliente). 2. Mientras, el consumidor intenta alcanzar a un mercader. 3. Una vez hay un mercader y un consumidor, estos entran en una disputa por el precio de la mercanca. 4. Si no hay mercanca y el precio ha sido acordado, ambos esperan. 5. En el momento en que haya una mercanca no tienen que esperar ms. 6. El mercader coge la mercanca. 7. El consumidor recoge la mercanca que el mercader le entrega. 8. El consumidor recoge la mercanca que el mercader le entrega. 9. El consumidor debe ir a la posicin origen donde consumir la mercanca y volver al primer paso. Todo esto se implementar en una hebra, y se llamarn a los mtodos de la clase anterior y de la clase Comprador (regatear, caminar, etc.). De tal forma que podemos ver que la interaccin del programa puede estar en el consumidor, ya que el productor solo pone las mercancas (y no tenemos ningn personaje encargado de colocarlas). As, el consumidor quedara de esta forma:

class Consumidor(threading.Thread):

Prctica 2. Programacin Concurrente.

24

def __init__(self, buff, comp, vend): # Llamada al segundo constructor threading.Thread.__init__(self) self.bb = buff self.comprador = comp self.vendedor = vend # Metodo run def run (self): item = 0 while 1: while self.comprador.posicion()[0] < 340: # Llega a la parte de la derecha self.comprador.caminar(0) while self.comprador.ocupado(): time.sleep(0) while self.comprador.ocupado(): # Esperamos a que llegue time.sleep(0) self.vendedor.regatear() # El tendero regatea self.comprador.regatear() # El comprador regatea con el tendero while self.vendedor.ocupado(): time.sleep(0) while self.comprador.ocupado(): time.sleep(0) self.vendedor.coger() # El tendero coge la mercancia self.vendedor.soltar() # El tendero suelta while self.vendedor.ocupado(): time.sleep(0) self.comprador.coger() # Coge la mercancia while self.comprador.ocupado(): time.sleep(0) item = self.bb.extraer() while self.comprador.posicion()[0] > 40: # Llega al tope izquierdo self.comprador.caminar(1) # Vuelve a la izquierda while self.comprador.ocupado(): time.sleep(0) while self.comprador.ocupado(): # Espera a estar libre para volver time.sleep(0)

Un personaje ocupado no puede atender ninguna otra tarea, por tanto tenemos que tenerlo

Prctica 2. Programacin Concurrente.

25

en cuenta en la planicacin.

Ejercicio propuesto:
Realiza este mismo problema para varios consumidores y varios productores.

3.3. El problema de los fumadores.


Considerar un estanco en el que hay tres fumadores y un estanquero. Cada fumador continuamente la un cigarro y se lo fuma. Para liar un cigarro, el fumador necesita tres ingredientes: tabaco, papel y cerillas. Uno de los fumadores tiene solamente papel, otro tiene solamente tabaco, y el otro tiene solamente cerillas. El estanquero tiene una cantidad innita de los tres ingredientes. El estanquero coloca aleatoriamente dos ingredientes diferentes de los tres que se necesitan para hacer un cigarro, desbloquea al fumador que tiene el tercer ingrediente y despus se bloquea. El fumador desbloqueado toma los dos ingredientes del mostrador, se la un cigarro y fuma durante un tiempo, desbloqueando al estanquero cuando termine de fumar. El estanquero vuelve a poner dos ingredientes aleatorios en el mostrador, y se repite el ciclo.

Pon un par de sillas y la mesa, adems de los tres elementos que se necesitan para fumar. Puedes ayudarte de la imagen llamada elementos.png, busca en Internet cualquier imagen adicional que quieras aadir. Puedes usar al comprador de la parte anterior como estanquero cuando utilices los Sprites, revisa su hoja de Sprites.

Ejercicio propuesto:
Escribir un programa en Python que implemente el esquema de sincronizacin explicado. Se escribir una clase hebra Estanquero y otra Fumador, especicando en el constructor de esta ltima clase el ingrediente que tiene el fumador. La interaccin entre los fumadores y el estanquero ser resuelta mediante un monitor Estanco. Hacer primero la versin texto y luego ampliarlo mediante Sprites.

3.4. Control de trnsito de coches en un puente estrecho


En un pueblo se da que hay un puente sobre un ro. El puente es estrecho, as que el alcalde decide que, puesto que no caben dos vehculos, solamente haya un carril. Hay coches que vienen del norte y del sur, y ambos pretenden cruzar el puente.

Prctica 2. Programacin Concurrente.

26

Como slo existe un carril sobre dicho puente, en un momento dado, slo puede ser cruzado por uno o ms coches en la misma direccin (pero no en direcciones opuestas).

Ejercicio propuesto:
a) Implementar el cdigo del monitor que resuelve el problema del acceso al puente suponiendo que llega un coche del norte (sur) y cruza el puente si no hay otro coche del sur (norte) cruzando el puente en ese momento. b) Mejorar el monitor anterior, de forma que la direccin del traco a travs del puente cambie cada vez que lo hayan cruzado 10 coches en una direccin, mientras 1 ms coches estuviesen esperando cruzar el puente en direccin opuesta. Si se desea animar la resolucin de este problema, se recomienda buscar los elementos grcos de la animacin en pginas web relacionadas con Pygame. La estructura del monitor es la siguiente:

MONITOR GestionaTrafico VAR PROCEDIMIENTO PROCEDIMIENTO PROCEDIMIENTO PROCEDIMIENTO EntrarCocheDelNorte SalirCocheDelNorte EntrarCocheDelSur SalirCocheDelSur

Inicializacion

4. Prctica voluntaria: El problema del barbero durmiente.


El problema del barbero durmiente es representativo de cierto tipo de problemas reales: ilustra perfectamente la relacin de tipo cliente-servidor que a menudo aparece entre los procesos. Una barbera tiene dos puertas y unas cuantas sillas. Los clientes entran por una puerta y salen por otra. Debido a que la barbera es pequea, solamente un cliente o el barbero pueden moverse por el local en cada momento. El barbero continuamente sirve clientes, uno cada vez. Cuando no hay ningn cliente en la barbera, el barbero est durmiendo en su silla. Cuando un cliente entra en la barbera y encuentra al barbero durmiendo, lo despierta, se sienta en su silla, y espera a que el barbero termine de pelarlo. Si el barbero est ocupado cuando un nuevo cliente entra, el cliente se sienta en una silla de la sala de espera y espera a que el barbero lo despierte. Cuando el barbero termina de pelar al cliente actual, lo despierta, abre la puerta de salida, y espera a que el cliente salga y cierre la puerta para pasar al siguiente cliente. A continuacin, si hay clientes esperando, el barbero despierta a uno y espera a que se siente en la silla para pelarlo. Si no hay clientes esperando, el barbero se sienta en su silla y duerme hasta que llegue algn cliente y lo despierte.

Ejercicio propuesto:
Escribir un programa usando monitoresd y hebras Python para el problema del barbero durmiente. Los clientes y el barbero son hebras, y la barbera es un monitor que implementa la inter-

Prctica 2. Programacin Concurrente.

27

accin entre stos. Los clientes llaman a cortarPelo para obtener servicio del barbero, despertndolo o esperando a que termine con el cliente anterior. El barbero llama a siguienteCliente para esperar la llegada de un nuevo cliente y servirlo. Cuando termina de pelar al cliente actual llama a finCliente, indicndole que puede salir de la barbera y esperando a que lo haga para pasar al siguiente cliente. Extender el ejercicio para el caso de que existan mltiples barberos en la barbera, cada uno con su propia silla y es posible pelar tantos clientes al mismo tiempo como barberos hay. La estructura del monitor es la siguiente:

MONITOR GestionaTrafico VAR PROCEDIMIENTO cortarPelo PROCEDIMIENTO siguienteCliente PROCEDIMIENTO finCliente Inicializacion

También podría gustarte