Razón Artificial

La ciencia y el arte de crear videojuegos

Pygame VIII: Inteligencia artificial

Escrito por adrigm el 14 de febrero de 2010 en Desarrollo Videojuegos, Noticias, Programación | 23 Comentarios.

En el siguiente tema no introduciremos ningún concepto nuevo de Pygame, pero si usaremos un poco de todo lo usado en los anteriores tutoriales para crear la pala que controlará el ordenador, se podría decir que es una Inteligencia Artificial muy básica.

Creando la pala de la CPU

Esto se hace igual que como creamos la pala_jug o la bola, ponemos la siguiente línea en la función principal debajo de pala_jug.

pala_cpu = Pala(WIDTH - 30)

En su momento pala_jug le pasamos como parámetro el valor 30 que quería decir que el centerx estaba a 30 píxeles de el borde izquierdo, ahora le pasamos WIDTH – 30, es decir a 30 píxeles del borde derecho.

También debemos añadir la línea:

screen.blit(pala_cpu.image, pala_cpu.rect)

En el bucle principal, debajo de los demas blit, como siempre. Con esto ya tenemos nuestro Sprite puesto en pantalla.

Ahora vamos a hacer que detecte también las colisiones, para ello volvemos a actualizar el método actualizar de la clase Bola:

def actualizar(self, time, pala_jug, pala_cpu):
	self.rect.centerx += self.speed[0] * time
	self.rect.centery += self.speed[1] * time
	if self.rect.left <= 0 or self.rect.right >= WIDTH:
		self.speed[0] = -self.speed[0]
		self.rect.centerx += self.speed[0] * time
	if self.rect.top <= 0 or self.rect.bottom >= HEIGHT:
		self.speed[1] = -self.speed[1]
		self.rect.centery += self.speed[1] * time

	if pygame.sprite.collide_rect(self, pala_jug):
		self.speed[0] = -self.speed[0]
		self.rect.centerx += self.speed[0] * time

	if pygame.sprite.collide_rect(self, pala_cpu):
		self.speed[0] = -self.speed[0]
		self.rect.centerx += self.speed[0] * time

Como podemos ver añadimos otro párametro al método pala_cpu que servirá para añadirle el Sprite pala_cpu como hicimos con pala_jug y añadimos las tres últimas líneas que son idénticas a las anteriores salvo que ahora para pala_cpu.

Recordad pasarle el parametro nuevo a la línea que llama a la función actualizar de la bola en el bucle del juego. Así:

ball.update(time, pala_jug, pala_cpu)

Dotando de Inteligencia Artificial a la Pala

Bueno ahora viene lo interesante, lograr que la pala se mueva para golpear la bola, esto se hace definiendo otro método en la clase Pala al método lo llamaré ia.

def ia(self, time, ball):
	if ball.speed[0] >= 0 and ball.rect.centerx >= WIDTH/2:
		if self.rect.centery < ball.rect.centery:
			self.rect.centery += self.speed * time
		if self.rect.centery > ball.rect.centery:
			self.rect.centery -= self.speed * time

En la línea 1 vemos que recibe como siempre self y time y aparte recibe ball que es la bola, es necesario pues el método necesita conocer donde está la bola.

En la línea 2 comprobamos que ball.speed[0] >= 0, es decir, que la velocidad en el eje x de la pelota sea positiva, es decir, que la pelota se este moviendo hacia la derecha (hacia la pala de la cpu) y tambien comprueba que ball.rect.centerx >= WIDTH/2 es decir que el centro x de la pelota sea mayor o igual que el centro del tablero, es decir, que la pelota este en el campo de la cpu.

Por tanto la línea 2 es un condicional que comprueba que la pelota vaya hacia donde está la pala de la cpu y que este en su campo, sino, que no se mueva. Esto se hace para que la CPU no sea invencible y no llegue a todas las pelotas.

La línea 3 comprueba si el centery de la pelota es menor que el centery de la bola, es decir si la pala está más arriba que que la pelota en cullo caso, ejecuta la línea 4 que mueve la pala de la cpu hacia abajo.

Las líneas 5 y 6 hacen lo mismo, pero a la inversa como se ve a simple vista.

Ahora solo nos queda llamar a la ia junto con las llamadas actualizar de la bola y mover de la pala_cpu.

pala_cpu.ia(time, bola)

El código nos queda de la siguiente manera:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Módulos
import sys, pygame
from pygame.locals import *

# Constantes
WIDTH = 640
HEIGHT = 480

# Clases
# ---------------------------------------------------------------------

class Bola(pygame.sprite.Sprite):
	def __init__(self):
		pygame.sprite.Sprite.__init__(self)
		self.image = load_image("images/ball.png", True)
		self.rect = self.image.get_rect()
		self.rect.centerx = WIDTH / 2
		self.rect.centery = HEIGHT / 2
		self.speed = [0.5, -0.5]

	def actualizar(self, time, pala_jug, pala_cpu):
		self.rect.centerx += self.speed[0] * time
		self.rect.centery += self.speed[1] * time
		if self.rect.left <= 0 or self.rect.right >= WIDTH:
			self.speed[0] = -self.speed[0]
			self.rect.centerx += self.speed[0] * time
		if self.rect.top <= 0 or self.rect.bottom >= HEIGHT:
			self.speed[1] = -self.speed[1]
			self.rect.centery += self.speed[1] * time

		if pygame.sprite.collide_rect(self, pala_jug):
			self.speed[0] = -self.speed[0]
			self.rect.centerx += self.speed[0] * time

		if pygame.sprite.collide_rect(self, pala_cpu):
			self.speed[0] = -self.speed[0]
			self.rect.centerx += self.speed[0] * time

class Pala(pygame.sprite.Sprite):
	def __init__(self, x):
		pygame.sprite.Sprite.__init__(self)
		self.image = load_image("images/pala.png")
		self.rect = self.image.get_rect()
		self.rect.centerx = x
		self.rect.centery = HEIGHT / 2
		self.speed = 0.5

	def mover(self, time, keys):
		if self.rect.top >= 0:
			if keys[K_UP]:
				self.rect.centery -= self.speed * time
		if self.rect.bottom <= HEIGHT:
			if keys[K_DOWN]:
				self.rect.centery += self.speed * time

	def ia(self, time, ball):
		if ball.speed[0] >= 0 and ball.rect.centerx >= WIDTH/2:
			if self.rect.centery < ball.rect.centery:
				self.rect.centery += self.speed * time
			if self.rect.centery > ball.rect.centery:
				self.rect.centery -= self.speed * time

# ---------------------------------------------------------------------

# Funciones
# ---------------------------------------------------------------------

def load_image(filename, transparent=False):
        try: image = pygame.image.load(filename)
        except pygame.error, message:
                raise SystemExit, message
        image = image.convert()
        if transparent:
                color = image.get_at((0,0))
                image.set_colorkey(color, RLEACCEL)
        return image

# ---------------------------------------------------------------------

def main():
	screen = pygame.display.set_mode((WIDTH, HEIGHT))
	pygame.display.set_caption("Pruebas Pygame")

	background_image = load_image('images/fondo_pong.png')
	bola = Bola()
	pala_jug = Pala(30)
	pala_cpu = Pala(WIDTH - 30)

	clock = pygame.time.Clock()

	while True:
		time = clock.tick(60)
		keys = pygame.key.get_pressed()
		for eventos in pygame.event.get():
			if eventos.type == QUIT:
				sys.exit(0)

		bola.actualizar(time, pala_jug, pala_cpu)
		pala_jug.mover(time, keys)
		pala_cpu.ia(time, bola)
		screen.blit(background_image, (0, 0))
		screen.blit(bola.image, bola.rect)
		screen.blit(pala_jug.image, pala_jug.rect)
		screen.blit(pala_cpu.image, pala_cpu.rect)
		pygame.display.flip()
	return 0

if __name__ == '__main__':
	pygame.init()
	main()

Ahora el juego es jugable, pero no tiene ni sistema de puntuación, ni sonidos ni nada de nada. Lo solucionaremos en los próximos tutoriales.

23 Comentarios en "Pygame VIII: Inteligencia artificial"

  1. Josep Valls dice:

    Estaba buscando información sobre agentes en python y topé con tu artículo.
    No se debe mezclar el concepto de inteligencia (artificial) con el de comportamiento (complejo).
    http://es.wikipedia.org/wiki/Inteligencia_artificial
    http://es.wikipedia.org/wiki/Comportamiento

  2. admin dice:

    Josep Valls, ciertamente esto no es IA, pero como es artículo va dedicado a la creación de videojuegos no era cuestión de entrar en materia.

    Aunque en realidad no está bien claro lo que se entiende por inteligencia artificial, te dejo un artículo que escribí hace tiempo con hasta 8 definiciones del concepto IA.

    http://razonartificial.com/2010/01/que-es-inteligencia-artificial/

  3. Paco dice:

    No se si te lo habran dicho en otros post…pero imagenes del juego, en cada uno de los apartados, segun lo vas haciendo seria bastante interesante, porque vas viendo la creacion del juego poco a poco. Ademas, creo que ganarias muchisimas mas visitas y nuevos desarrolladores.

    Paco.

    PD: Una imagen vale mas que mil palabras…

  4. admin dice:

    Paco, le tendré en cuenta, pero como de verdad se aprende es probando tu el código, modificándolo y experimentando por ti mismo. Es por eso que no considero necesario poner capturas.

    Además al final de cada tutorial pongo todo el código para que con solo ejecutarlo lo veas funcionar.

  5. Pau dice:

    Muchas gracias por el tutorial!! No por el idioma ya que la verdad es que me he leído ya muchos en inglés (aunque ayuda que este en español) sino por las explicaciones de las cosas. Por ejemplo, en ningun tutorial en inglés de los que me he leído he encontrado la explicación de qué era self.rect.image y en la documentación de pygame tampoco quedaba bastante claro (http://www.pygame.org/docs/ref/sprite.html#pygame.sprite.Sprite).
    En cambio tu, aunque brevemente, haces una sencilla explicación de lo que es y para lo que sirve y qué parametros más se pueden encontrar cosa que me ha permitido más fluidez para modificar cosas sin tener que copiarlas de otros códigos.

    Por cierto, donde podría encontrar algo más de información sobre los rects?

  6. admin dice:

    Pau, en la documentación traducida de Pygame que hizo loserjuegos hay bastante sobre los rects. Te dejo un enlace: http://www.losersjuegos.com.ar/traducciones/pygame/rect

    De todas maneras cualquier duda sobre ellos comenta y tratare de resolverla.

  7. Olimpiodoro dice:

    Una cosa, el juego es jugable pero, ¿es ganable? Me refiero, es imposible que la CPU se equivoque. ¿Cómo ganar el juego?

  8. admin dice:

    Olimpiodoro, Hay varias técnicas para bajar la dificultad, haciendo que la reacción de la pala sea por ejemplo cuando este casi llegando a su lado, o simplemente disminuyendo la velocidad de su pala o subiendo la de la pelota.

    Hay varias cosas para hacer una I.A. mucho más realista, pero no entran en este tutorial en el que se pretende enseñar la base de Pygame y no técnicas de I.A.

  9. Andrés dice:

    Para ahorrar un par de líneas de código te dejo mi función de actualizar

            def actualizar(self, time, colisiones):
                    self.rect.centerx += self.speed[0] * time
                    self.rect.centery += self.speed[1] * time
                    if self.rect.left = WIDTH:
                            self.speed[0] = -self.speed[0]
                            self.rect.centerx += self.speed[0] * time
                    if self.rect.top = HEIGHT:
                            self.speed[1] = -self.speed[1]
                            self.rect.centery += self.speed[1] * time
    
                    for elemento in colisiones:
                            if pygame.sprite.collide_rect(self, elemento):
                                    self.speed[0] = -self.speed[0]
                                    self.rect.centerx += self.speed[0] * time
    

    luego las colisiones las agregamos en bola:

    bola.actualizar(time, [pala_jug, pala_cpu])
    

    y podemos añadir colisiones a veintemil y un elementos:

    bola.actualizar(time, [pala_jug, pala_cpu, cosa, que, se, me, ocurra, en, el, array])
    

    ¿¿pero una pregunta puedo crear colisiones con límites que yo defina y que no se dibujen en la pantalla??.

    PD: El mejor tutorial de pygame que he encontrado. Muy

  10. Andrés dice:

    nota sobre el comentario anterior.

    La indentación no me la tomó. deberán imaginarla

  11. adrigm dice:

    Ya está arreglado tu comentario, utiliza las etiquetas [“python][“/python”] (sin comillas) para poner código.

    En cuanto a la pregunta no entiendo bien, ¿te refieres a los rects, a que no sean cuadrados?

  12. Andrés dice:

    Gracias por la aclaración.

    Respecto de la pregunta, me explico. Si puedo definir un cuadrado de cuatro puntos arbitrarios ej.: (20,20)(20,40)(40,40)(40,20)

    Y crear una colisión con esta figura. O dicho de otro modo, que colisione con rects definidos arbitrariamente y no tomados de un archivo.

  13. adrigm dice:

    Tú el rect asociado a un sprite puedes definirlo del tamaño que quieres. Puedes acceder a sus variables con rect.x, rect.left, rect.center, etc. Cada una modifica las anteriores.

  14. Jogui dice:

    Adrigm, el método de la clase pala de la ia, todos los balls no habrían de ser traducidos por bola ( vi que ya lo comentabas en otro post del tutorial). Supongo que ese metodo no funcionaria si no se traduce no?

  15. adrigm dice:

    Puedes traducirlo perfectamente a bola, como ya he comentado el juego inicialmente lo tenía en inglés y traduje las variables cuando hice el tutorial, pero algunas se me pasaron.

  16. cristianZ dice:

    gracias por el tuto muy bueno tengo ,un problema con una sentencia :

    AttributeError :’module’ object has no attribute ‘collide_rect’

    supongo que es por la distrubución estoy utilizando la python 2.4

  17. adrigm dice:

    Pues si no pones la línea del error y demás… De todas maneras trata de usar la versión 2.6.x

  18. cristianZ dice:

    no era por la version en versiones anteriores se declara pygame.sprite.spritecollide

    en lugar de
    pygame.sprite.collide_rect

    para invocar la funcion de colición.
    gracias.

  19. Kurai dice:

    cristianZ me pasa lo mismo, pero ha de ser la version de pygame [la funcion es del modulo, no de python :P]

    con pygame.sprite.spritecollide el codigo deberia quedar parecido a este:

    def actualizar(self, time, pala_jug, pala_cpu):
            grup = pygame.sprite.Group(pala_jug, pala_cpu)
            self.rect.centerx += self.speed[0] * time
            self.rect.centery += self.speed[1] * time
            if self.rect.left <= 0 or self.rect.right >= WIDTH:
                self.speed[0] = -self.speed[0]
                self.rect.centerx += self.speed[0] * time
            if self.rect.top <= 0 or self.rect.bottom >= HEIGHT:
                self.speed[1] = -self.speed[1]
                self.rect.centery += self.speed[1] * time
            if pygame.sprite.spritecollide(self, grup, False):
                self.speed[0] = -self.speed[0]
                self.rect.centerx += self.speed[0] * time
    

    El codigo no especifica con cual sprite se colisiono, pero a efectos de este game, no hace falta, solo necesitas saber si la colision se dio para cambiar la direccion de la bola.

    Saludos.

  20. Es uno de los mejores tutoriales que he visto, me inicio en el mundo de los videojuego: en parte por mi profesión de educador e informático, pero tengo hijos de 5 y 6 años, a quienes quiero sorprender con “el juego que hizo papá”; más, montones de estudiantes y yo no salíamos de los softwares administrativos.

    Hoy acá en Venezuela se quiere dar un impulso a todo lo que es el criterio educativo y debe aplicarse a los videojuegos para acelerar la alfabetización (letras, números, sus operaciones, los valores)desde temprana edad. Por lo cual no nos sirve copiar juegos solamente. Así que hoy veo una herramienta que me permite contextualizar según los animales y frutas de este país, entre otros elementos. Lo que quiero decir es gracias y felicitaciones sinceras. La emoción se debe a que ya conozco python, así que me viene como anillo al dedo…

  21. […] Pygame VIII: Inteligencia artificial […]

  22. Javier Robles dice:

    He llegado hasta este punto del tutorial pero cuando intento iniciar el juego sale un error en la terminal.
    Traceback (most recent call last):
    File “plantilla para pygame(Pong).py”, line 113, in
    main()
    File “plantilla para pygame(Pong).py”, line 101, in main
    bola.actualizar(time, pala_jug, pala_cpu)
    TypeError: actualizar() takes exactly 3 arguments (4 given)
    Hace referencia a la linea de actualizar la bola, pero lo he comprobado una y otra vez y los codigos son identicos a los tuyos. Ayudaaaaa! D:

    Te dejo mi codigo para que lo revises.

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    # Módulos
    import sys, pygame
    from pygame.locals import *

    # Constantes
    WIDTH = 640
    HEIGHT = 480

    # Clases
    # ———————————————————————

    class Bola(pygame.sprite.Sprite):
    def __init__(self):
    pygame.sprite.Sprite.__init__(self)
    self.image = load_image(“images/ball.png”, True)
    self.rect = self.image.get_rect()
    self.rect.centerx = WIDTH / 2
    self.rect.centery = HEIGHT / 2
    self.speed = [0.5, -0.5]

    def actualizar(self, time, pala_jug):
    self.rect.centerx += self.speed[0] * time
    self.rect.centery += self.speed[1] * time
    if self.rect.left = WIDTH:
    self.speed[0] = -self.speed[0]
    self.rect.centerx += self.speed[0] * time
    if self.rect.top = HEIGHT:
    self.speed[1] = -self.speed[1]
    self.rect.centery += self.speed[1] * time

    if pygame.sprite.collide_rect(self, pala_jug):
    self.speed[0] = -self.speed[0]
    self.rect.centerx += self.speed[0] * time

    if pygame.sprite.collide_rect(self, pala_cpu):
    self.speed[0] = -self.speed[0]
    self.rect.centerx += self.speed[0] * time

    class Pala(pygame.sprite.Sprite):
    def __init__(self, x):
    pygame.sprite.Sprite.__init__(self)
    self.image = load_image(“images/pala.png”)
    self.rect = self.image.get_rect()
    self.rect.centerx = x
    self.rect.centery = HEIGHT / 2
    self.speed = 0.5

    def mover(self, time, keys):
    if self.rect.top >= 0:
    if keys[K_UP]:
    self.rect.centery -= self.speed * time
    if self.rect.bottom = 0 and ball.rect.centerx >= WIDTH/2:
    if self.rect.centery ball.rect.centery:
    self.rect.centery -= self.speed * time

    # ———————————————————————

    # Funciones
    # ———————————————————————

    def load_image(filename, transparent=False):
    try: image = pygame.image.load(filename)
    except pygame.error, message:
    raise SystemExit, message
    image = image.convert()
    if transparent:
    color = image.get_at((0,0))
    image.set_colorkey(color, RLEACCEL)
    return image

    # ———————————————————————

    def main():
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption(“Pruebas Pygame”)

    background_image = load_image(‘images/fondo_pong.png’)
    bola = Bola()
    pala_jug = Pala(30)
    pala_cpu = Pala(WIDTH -30)

    clock = pygame.time.Clock()

    while True:
    time = clock.tick(60)
    keys = pygame.key.get_pressed()
    for eventos in pygame.event.get():
    if eventos.type == QUIT:
    sys.exit(0)

    bola.actualizar(time, pala_jug, pala_cpu)
    pala_jug.mover(time, keys)
    pala_cpu.ia(time, bola)
    screen.blit(background_image, (0, 0))
    screen.blit(bola.image, bola.rect)
    screen.blit(pala_jug.image, pala_jug.rect)
    screen.blit(pala_cpu.image, pala_cpu.rect)
    pygame.display.flip()
    return 0

    if __name__ == ‘__main__':
    pygame.init()
    main()

  23. Javier Robles dice:

    Resulta que no era igual, me faltaba una referencia a las pala_cpu en la clase Bola. :P

Deja un comentario