Choque de bolas (pygame)

Solicite, consulte o publique recursos de referencia para desarrolladores.

Choque de bolas (pygame)

Notapor endaramiz » Mar Ene 15, 2008 6:04 pm

Este es un ejemplo básico de simulación física de choques totalmente elásticos en bolas con la misma masa.

Lo de ejemplo básico es porque no tiene interacción con el usuario, los cambios se tienen que hacer desde el código, y no tiene archivos de imagen ni de fondo, ya que así lo he visto mas conveniente.

Me hubiese gustado explicarlo pero voy falto de tiempo y no se cuando lo hubiese acabado. Si alguien quiere hacerlo por mi... no me enfadaré :D

Soy novato, no solo en lo de los videojuegos sino en la programación en general, por lo tanto, lo mas normal es que mi código no estará del todo bien. Si tenéis sugerencias para mejorarlo, me gustaría que las expongáis.

Código: Seleccionar todo
# -*- coding: utf-8 -*-
#
# Autor: David Ramírez
# Licencia: GPL 2

import pygame
from pygame.locals import *
from math import *

width = 500
height = 500

def detectar_colisiones (bolas):
   bolas1 = list()
   colisiones = list()
   for bola1 in range (len(bolas)):
      bolas1.append(bola1)
      for bola2 in range (len(bolas)):
         if bola2 not in bolas1:
            if hypot ((bolas[bola2][1][0]-bolas[bola1][1][0]), (bolas[bola1][1][1]-bolas[bola2][1][1])) < (bolas[bola1][0] + bolas[bola2][0]):
               colisiones.append([bolas[bola1],bolas[bola2]])
   return colisiones

def limites (screen,bolas):
   for bola in bolas:
      if bola[1][0]-bola[0] < 0 and bola[2][0] < 0:
         bola[2][0] = -bola[2][0]
      elif bola[1][0]+bola[0] > width and bola[2][0] > 0:
         bola[2][0] = -bola[2][0]
      if bola[1][1]-bola[0] < 0 and bola[2][1] > 0:
         bola[2][1] = -bola[2][1]
      elif bola[1][1]+bola[0] > height and bola[2][1] < 0:
         bola[2][1] = -bola[2][1]

def colision_bolas (bola1, bola2):
   if bola2[1][0]-bola1[1][0] == 0:
      a = pi/2.0
   else:
      a = atan ((bola1[1][1]-bola2[1][1])/(bola2[1][0]-bola1[1][0]))
   v1r = bola1[2][0]*cos(-a)-(bola1[2][1])*sin(-a)
   v1s = bola1[2][0]*sin(-a)+(bola1[2][1])*cos(-a)
   v2r = bola2[2][0]*cos(-a)-(bola2[2][1])*sin(-a)
   v2s = bola2[2][0]*sin(-a)+(bola2[2][1])*cos(-a)
   v1r, v2r = v2r, v1r
   bola1[2][0] = v1r*cos(a)-v1s*sin(a)
   bola1[2][1] = (v1r*sin(a))+(v1s*cos(a))
   bola2[2][0] = v2r*cos(a)-v2s*sin(a)
   bola2[2][1] = (v2r*sin(a))+(v2s*cos(a))

def actualizar_posicion(bolas):
   for bola in bolas:
      bola[1][0] += bola[2][0]
      bola[1][1] -= bola[2][1]

def actualizar_pantalla(screen,bolas):
   screen.fill((0,0,0))
   for bola in bolas:
      pygame.draw.circle(screen, (0,255,0), (int(bola[1][0]),int(bola[1][1])), int(bola[0]))
   pygame.display.flip()

def main():
   screen = pygame.display.set_mode((width,height))
   bolas = list()
   #bolas.append([Radio,[Posicion X, Posicion Y],[Velocidad X, Velocidad Y]])
   bolas.append([25.0,[400.0,300.0],[1.5,0.3]])
   bolas.append([25.0,[410.0,50.0],[1.0,0.0]])
   bolas.append([25.0,[240.0,50.0],[-0.0,0.0]])
   bolas.append([25.0,[360.0,50.0],[1.0,0.0]])
   bolas.append([25.0,[290.0,50.0],[-0.0,0.0]])
   clock = pygame.time.Clock()
   while True:
      clock.tick(300)
      actualizar_pantalla(screen,bolas)
      colisiones = detectar_colisiones(bolas)
      for colision in colisiones:
         colision_bolas(colision[0],colision[1])
      actualizar_posicion(bolas)
      limites(screen,bolas)

if __name__ == '__main__': main()


PD: no se yo si a alguien le resultará útil... pero ya que lo tenía hecho, lo comparto por si acaso.
PD2: en ejemplos de esta web he visto algo de colisiones pero escrito en c o c++ (creo, no lo se) y hay gente (como yo) que no entiende ese lenguaje.

Saludos.
Avatar de Usuario
endaramiz
 
Mensajes: 283
Registrado: Vie Ago 31, 2007 9:25 am
Ubicación: Barcelona

Notapor hugoruscitti » Lun Ene 21, 2008 8:12 pm

Hola, me gusta mucho como funciona el programa, es muy realista, parecen bolas
de billard rebotando (sin detenerse).

Con respecto a las sugerencias, tengo solo dos para realizar.

1. Obsevo que la información de cada bola está almacenada como una lista, cada
atributo es una posición de esta lista:

Código: Seleccionar todo
bolas.append([60.0,[400.0,300.0],[6.5,0.3]])


el primero es el radio, lo segundo es la posición inicial y el tercero es
la velocidad de movimiento.

Creo que es mas sencillo cuando el código se desarrolla en base a entidades
como objetos, donde cada atributo queda representado por un nombre, y no una
posición especifica. En este caso podría ser:

Código: Seleccionar todo
bola = Bola(x=400, y=300, radio=60)
bola.empujar(6.5, 0.3)


O directamente:

Código: Seleccionar todo
bola = Bola(400, 300, 60) ...


si quieres omitir el nombre de los argumentos.

Luego, a cada elemento 'Bola' puedes añadirlo a una lista de sprites
para mostrar en pantalla.

2. Existe una recomendación de estilo que siguen muchos programadores
de python. La idea consiste en seguir algunas reglas sencillas al momento de
escribir, buscando que el código escrito sea mas fácil de leer y modificar.

Estas sugerencias incluyen dejar espacios entre operadores,
como así también usar tabulaciones de 4 espacios y muchas
cosas mas. Te dejo el artículo que explica estas convenciones y
su traducción al español:

http://mundogeek.net/traducciones/guia- ... python.htm
http://www.python.org/dev/peps/pep-0008/

Lo bueno de seguir esta recomendación se nota cuando lees o escribes código
con otras personas, hace mas consistente el intercambio de código.



Para mostrar estas ideas traté de hacer una versión modificada de tu código.
Lamentablemente, parece que copié algo mal, porque las esferas a veces
colisionan de manera incorrecta. Te paso el código al menos a modo
ilustrativo:

Imagen

Código: Seleccionar todo
# -*- coding: utf-8 -*-
#
# Autor: David Ramírez
# Modificaciones: Hugo Ruscitti
# Licencia: GPL 2

import pygame
from pygame.locals import *
from math import *
from pygame.sprite import Sprite

width = 500
height = 200

class Bola(Sprite):
    "Representa una bola que rebota en pantalla."

    def __init__(self, x, y, radio, color=(0, 255, 0)):
        Sprite.__init__(self)
        self.x, self.y = x, y
        self.radio = radio
        self._crear_imagen(radio, color)
        self.rect = self.image.get_rect(self.image)
        self.empujar(0, 0)

    def _crear_imagen(self, radio, color):
        ancho = alto = radio * 2
        self.image = pygame.Surface((ancho, alto)).convert()
        self.image.set_colorkey((0, 0, 0))
        pygame.draw.circle(self.image, color, (radio, radio), radio)

    def update(self):
        self.x += self.dx
        self.y += self.dy
        self.rect.center = (self.x, self.y)
        self.verificar_limites()

    def verificar_limites(self):
        "Evita que una bola salga de la pantalla."

        if self.rect.left < 0:
            self.rect.left = 0
            self.dx *= -1
        elif self.rect.right > width:
            self.rect.right = width
            self.dx *= -1

        if self.rect.top < 0:
            self.rect.top = 0
            self.dy *= -1
        elif self.rect.bottom > height:
            self.rect.bottom = height
            self.dy *= -1

    def empujar(self, dx, dy):
        self.dx = dx
        self.dy = dy

    def colisiona_con(self, otra):
        """Reacciona si existe una colision con otra bola.

        Retorna True si se produce la colisión, False en caso contrario."""
        x, y, r = self.x, self.y, self.radio

        if hypot((x - otra.x), (y - otra.y)) < r + otra.radio:
            if otra.x - self.x == 0:
                a = pi / 2.0
            else:
                a = atan((self.y - otra.y) / (otra.x - self.x))

            v1r = self.dx * cos(-a) - (self.dy) * sin(-a)
            v1s = self.dx * sin(-a) + (self.dy) * cos(-a)
            v2r = otra.dx * cos(-a) - (otra.dy) * sin(-a)
            v2s = otra.dx * sin(-a) + (otra.dy) * cos(-a)
            v1r,  v2r = v2r,  v1r
            self.dx = v1r * cos(a) - v1s * sin(a)
            self.dy = (v1r * sin(a)) + (v1s * cos(a))
            otra.dx = v2r * cos(a) - v2s * sin(a)
            otra.dy = (v2r * sin(a)) + (v2s * cos(a))
            return True
        else:
            return False

def informar_colisiones(bolas):
    copia_grupo = pygame.sprite.Group(bolas)

    for a in bolas:
        for b in copia_grupo:
            if a != b and a.colisiona_con(b):
                copia_grupo.remove(a)
                copia_grupo.remove(b)

def actualizar_pantalla(screen, bolas):
    screen.fill((0, 0, 0))
    bolas.draw(screen)
    pygame.display.flip()

def main():
    screen = pygame.display.set_mode((width, height))
    bolas = pygame.sprite.Group()

    b1 = Bola(x=400, y=100, radio=10)
    b1.empujar(-1.5, 0.4)

    b2 = Bola(250, 100, 20, color=(255, 0, 0))
    b2.empujar(1.2, 0.4)

    b3 = Bola(40, 100, 10, color=(100, 0, 250))
    b3.empujar(1.5, 0.3)

    bolas.add([b1, b2, b3])

    clock = pygame.time.Clock()

    while True:
         
        for e in pygame.event.get():
            if e.type == QUIT:
                return
           
        clock.tick(300)
        actualizar_pantalla(screen, bolas)
        bolas.update()
        colisiones = informar_colisiones(bolas)

if __name__ == '__main__':
    main()


Saludos.
Avatar de Usuario
hugoruscitti
Site Admin
 
Mensajes: 1242
Registrado: Dom Jul 30, 2006 3:57 am
Ubicación: Buenos Aires, Argentina

Notapor endaramiz » Vie Ene 25, 2008 7:12 pm

Muchas graciasss!!! :D Esto es lo que quería... ya tuve un problema con un intento de tetris que lo tengo que empezar desde el principio, cuando ya hacía bastantes cosas, porque me puse a escribir código sobre la marcha sin pararme a pensar mucho en las ideas generales.


hugoruscitti escribió:http://mundogeek.net/traducciones/guia-estilo-python.htm
http://www.python.org/dev/peps/pep-0008/
Le he echado un vistazo y esto también me será muy útil, incluso para practicar mi inglés que he notado que es de gran ayuda.


hugoruscitti escribió:Hola, me gusta mucho como funciona el programa, es muy realista, parecen bolas
de billard rebotando (sin detenerse).
Me alegro de que te guste. Yo también pensé lo mismo que tu y poco después de hacer eso me puse a trabajar en choque con rectas, que puede servir no solo para billard sino minigolf, pinball... tengo bastantes ideas en la cabeza y algunos cálculos en papel pero ahora falta pasarlo al ordenador y que funcione. Ya lo publicaré si lo consigo. Supongo que ahora irá mas rápido el desarrollo porque he tenido que repasar un libro de matemáticas olvidado y además estoy pasando algunos exámenes.


hugoruscitti escribió:Lo bueno de seguir esta recomendación se nota cuando lees o escribes código
con otras personas, hace mas consistente el intercambio de código.

Para mostrar estas ideas traté de hacer una versión modificada de tu código.
Lamentablemente, parece que copié algo mal, porque las esferas a veces
colisionan de manera incorrecta. Te paso el código al menos a modo
ilustrativo:
Pues las dos cosas están relacionadas, cuando escribía el código lo hice pensando en el centro de coordenadas en la esquina inferior izquierda para no crear conflictos con el sinus y luego RESTABA la "velocidad Y" pero ahora me doy cuenta que sería mejor seguir unas pautas y he cambiado unos signos en el método "colisiona_con" para que las dos velocidades se sumen.
También he añadido unas lineas para solucionar un bug que encontré: habían veces (muy pocas) que una bola se quedaba "atrapada" en un llado, supongo que porque chocaba contra la pared y otra bola al mismo tiempo. Me costó de encontrar porque mirando el código, a simple vista parece que con lo de poner el rectángulo dentro de la pantalla ya se solucione, pero también hay que poner las posiciones "self.x" y self.y". Se que es algo rebuscado y espero que no te siente mal ya que no era mi intención buscarte fallos, como ya dije antes, tu código me ha servido de gran ayuda.


Copio todo entero que creo que será mas cómodo que no cosas sueltas y además se me puede olvidar alguna cosa.
Código: Seleccionar todo
# -*- coding: utf-8 -*-
#
# Autor: David Ramírez
# Modificaciones: Hugo Ruscitti
# Licencia: GPL 2

import pygame
from pygame.locals import *
from math import *
from pygame.sprite import Sprite

width = 500
height = 200

class Bola(Sprite):
    "Representa una bola que rebota en pantalla."

    def __init__(self, x, y, radio, color=(0, 255, 0)):
        Sprite.__init__(self)
        self.x, self.y = x, y
        self.radio = radio
        self._crear_imagen(radio, color)
        self.rect = self.image.get_rect(self.image)
        self.empujar(0, 0)

    def _crear_imagen(self, radio, color):
        ancho = alto = radio * 2
        self.image = pygame.Surface((ancho, alto)).convert()
        self.image.set_colorkey((0, 0, 0))
        pygame.draw.circle(self.image, color, (radio, radio), radio)

    def update(self):
        self.x += self.dx
        self.y += self.dy
        self.rect.center = (self.x, self.y)
        self.verificar_limites()

    def verificar_limites(self):
        "Evita que una bola salga de la pantalla."

        if self.rect.left < 0:
            self.rect.left = 0
            self.x = self.radio
            self.dx *= -1
        elif self.rect.right > width:
            self.rect.right = width
            self.x = width - self.radio
            self.dx *= -1

        if self.rect.top < 0:
            self.rect.top = 0
            self.y = self.radio
            self.dy *= -1
        elif self.rect.bottom > height:
            self.rect.bottom = height
            self.y = height - self.radio
            self.dy *= -1

    def empujar(self, dx, dy):
        self.dx = dx
        self.dy = dy

    def colisiona_con(self, otra):
        """Reacciona si existe una colision con otra bola.

        Retorna True si se produce la colisión, False en caso contrario."""
        x, y, r = self.x, self.y, self.radio

        if hypot((x - otra.x), (y - otra.y)) < r + otra.radio:
            if otra.x - self.x == 0:
                a = pi / 2.0
            else:
                a = atan((self.y - otra.y) / (otra.x - self.x))

            v1r = self.dx * cos(-a) - (-self.dy) * sin(-a)
            v1s = self.dx * sin(-a) + (-self.dy) * cos(-a)
            v2r = otra.dx * cos(-a) - (-otra.dy) * sin(-a)
            v2s = otra.dx * sin(-a) + (-otra.dy) * cos(-a)
            v1r,  v2r = v2r,  v1r
            self.dx = v1r * cos(a) - v1s * sin(a)
            self.dy = -(v1r * sin(a) + v1s * cos(a))
            otra.dx = v2r * cos(a) - v2s * sin(a)
            otra.dy = -(v2r * sin(a) + v2s * cos(a))
            return True
        else:
            return False

def informar_colisiones(bolas):
    copia_grupo = pygame.sprite.Group(bolas)

    for a in bolas:
        for b in copia_grupo:
            if a != b and a.colisiona_con(b):
                copia_grupo.remove(a)
                copia_grupo.remove(b)

def actualizar_pantalla(screen, bolas):
    screen.fill((0, 0, 0))
    bolas.draw(screen)
    pygame.display.flip()

def main():
    screen = pygame.display.set_mode((width, height))
    bolas = pygame.sprite.Group()

    b1 = Bola(x=400, y=100, radio=10)
    b1.empujar(-1.5, 0.4)

    b2 = Bola(250, 100, 20, color=(255, 0, 0))
    b2.empujar(1.2, 0.4)

    b3 = Bola(40, 100, 10, color=(100, 0, 250))
    b3.empujar(1.5, 0.3)

    bolas.add([b1, b2, b3])

    clock = pygame.time.Clock()

    while True:
         
        for e in pygame.event.get():
            if e.type == QUIT:
                return
           
        clock.tick(300)
        actualizar_pantalla(screen, bolas)
        bolas.update()
        colisiones = informar_colisiones(bolas)

if __name__ == '__main__':
    main()


PD: que hace el return este? bueno... mas bien, como? (ya que supongo que sirve para cerrar el programa cuando por ejemplo haces clic en la X)
Código: Seleccionar todo
        for e in pygame.event.get():
            if e.type == QUIT:
                return

Saludos.
Avatar de Usuario
endaramiz
 
Mensajes: 283
Registrado: Vie Ago 31, 2007 9:25 am
Ubicación: Barcelona

Notapor hugoruscitti » Dom Ene 27, 2008 8:45 pm

Hola dvd, estoy muy contento al ver que has podido resolver el problema
que me surgió al modificar el código. ¡Ahora funciona perfecto!.

Me preguntaba si tal vez te gustaría colocar este ejemplo en la web, al menos
yo quisiera que esté ahí (queda a elección tuya, sin presiones). Imagino que
incluso se podría colocar una versión adicional, añadiendo sonidos a los
rebotes.


dvd escribió:PD: que hace el return este? bueno... mas bien, como? (ya que supongo que sirve para cerrar el programa cuando por ejemplo haces clic en la X)
Código: Seleccionar todo
        for e in pygame.event.get():
            if e.type == QUIT:
                return


El motivo del 'return' es terminar inmediatamente el bucle de juego; como
'return' está dentro de la función 'main', significa que terminará la
ejecución del programa.

Hay muchas otras formas de hacer esto igualmente, lo mas habitual es contar
con una variable adicional que indique el estado de ejecución del programa:

Código: Seleccionar todo
def main():
    running = True

    while running:
        actualizar_logica()
        mostrar_graficos()

        if existe_algun_motivo_para_salir:
            running = False


o lo que produce el mismo resultado:

Código: Seleccionar todo
def main():
    while True:
        actualizar_logica()
        mostrar_graficos()

        if existe_algun_motivo_para_salir:
            return


Nota que 'existe_algun_motivo_para_salir' podría ser cualquier condición
que se nos ocurra, en muchos juegos suele ser la pulsación de una tecla
como Escape, el cierre de la ventana principal, o haber seleccionado la
opción 'salir' en algún menú.

En el código anterior busqué terminar el programa cuando el usuario
pulse el botón para cerrar la ventana. Pygame notifica todos los eventos
que llegan a la ventana mediante el módulo 'event'. Por ejemplo, la
llamada a:

Código: Seleccionar todo
pygame.event.get()


devuelve una lista de todos los eventos que se registraron en la ventana desde
la anterior ejecución de esta misma sentencia. Cada uno de los elementos de
esta lista es un objeto "Event" como los de la biblioteca SDL. Así que para
distinguirlos solo tienes que consultar su atributo 'type'. Dejo un ejemplo
ilustrativo de como manejar los eventos de pulsación de tecla y cierre de
ventana:

Código: Seleccionar todo
import pygame

screen = pygame.display.set_mode((320, 240))
running = True

while running:
    lista_de_eventos = pygame.event.get()

    if lista_de_eventos:

        for evento in lista_de_eventos:
            if evento.type == pygame.QUIT:
                print "El usuario cerro la ventana"
                running = False
            elif evento.type == pygame.KEYDOWN:
                print "El usuario pulso la tecla", evento.key
            else:
                # Otro tipo de evento -> No hacer nada
                pass


Entonces, para resumir, en el código de la vez anterior 'return' termina
el bucle, 'event.type == QUIT' significa 'cuando el usuario cierra la
ventana' y 'pygame.event.get()' retorna todos los eventos que llegan
a la ventana. O algo así...



Un saludo grande, muy bueno tu programa de ejemplo. Reitero mis ganas de
que se publique en la sección ejemplos de la web.

Suerte.
Avatar de Usuario
hugoruscitti
Site Admin
 
Mensajes: 1242
Registrado: Dom Jul 30, 2006 3:57 am
Ubicación: Buenos Aires, Argentina

Notapor endaramiz » Mar Ene 29, 2008 7:19 pm

Buenas!
hugoruscitti escribió:El motivo del 'return' es terminar inmediatamente el bucle de juego; como
'return' está dentro de la función 'main', significa que terminará la
ejecución del programa.
Gracias, me tenía un poco desconcertado ya que era la primera vez que lo veo (a lo que estoy acostumbrado es al sys.exit() ). Ahora ya se otra manera mas de utilizar el return. :D


hugoruscitti escribió:Me preguntaba si tal vez te gustaría colocar este ejemplo en la web, al menos yo quisiera que esté ahí (queda a elección tuya, sin presiones). Imagino que incluso se podría colocar una versión adicional, añadiendo sonidos a los rebotes.
Pues no hay ningún problema! (bueno... siempre y cuando sea con la licencia libre, pero supongo que ya lo tienes en cuenta). Es mas, es todo un honor para mi, me alegro de que haya gustado a alguien de tu nivel.

Respecto a lo de la versión expandida tampoco me importa pero yo tengo problemas con el micrófono y creo que no podré grabar un sonido. Tu tienes algo hecho ya? (por cierto... quería haber contestado antes pero tenia que estudiar y soy lento escribiendo) El código para que suene el sonido creo que no serán mas de 5 líneas así que supongo que será fácil de hacer.

Saludos.
Avatar de Usuario
endaramiz
 
Mensajes: 283
Registrado: Vie Ago 31, 2007 9:25 am
Ubicación: Barcelona

Notapor hugoruscitti » Mié Ene 30, 2008 10:37 am

dvd escribió:
hugoruscitti escribió:Me preguntaba si tal vez te gustaría colocar este ejemplo en la web, al menos yo quisiera que esté ahí (queda a elección tuya, sin presiones). Imagino que incluso se podría colocar una versión adicional, añadiendo sonidos a los rebotes.
Pues no hay ningún problema! (bueno... siempre y cuando sea con la licencia libre, pero supongo que ya lo tienes en cuenta). Es mas, es todo un honor para mi, me alegro de que haya gustado a alguien de tu nivel.

Respecto a lo de la versión expandida tampoco me importa pero yo tengo problemas con el micrófono y creo que no podré grabar un sonido. Tu tienes algo hecho ya? (por cierto... quería haber contestado antes pero tenia que estudiar y soy lento escribiendo) El código para que suene el sonido creo que no serán mas de 5 líneas así que supongo que será fácil de hacer.

Saludos.


Perfecto, me alegra que pueda subir el ejemplo a la web. Mira, en
la siguiente dirección he subido el ejemplo:

http://www.losersjuegos.com.ar/referenc ... emplos.php

si puedes visita la sección y corrobora que todo esté bien. Apenas
tuve tiempo para tomar las capturas de pantalla y añadir un sonido
de un sitio web con sonidos de libre utilización
(http://freesound.iua.upf.edu/).

Lamentablemente no he tenido mucho tiempo para consultarte
antes de subir el ejemplo, pero igualmente, si encuentras algo
para corregir avisame y subo el cambio.

Nuevamente gracias por sumar tu ejemplo a la web.

PD: sobre la licencia, queda bajo GPLv2 como indicaste en el
principio del archivo.

Saludos
Avatar de Usuario
hugoruscitti
Site Admin
 
Mensajes: 1242
Registrado: Dom Jul 30, 2006 3:57 am
Ubicación: Buenos Aires, Argentina

Notapor endaramiz » Mié Ene 30, 2008 3:06 pm

Hola Hugo.


hugoruscitti escribió: si puedes visita la sección y corrobora que todo esté bien.
Lo he mirado y me ha parecido que todo está correcto. Y es buena idea poner el enlace al tema.



hugoruscitti escribió:Nuevamente gracias por sumar tu ejemplo a la web.
Gracias a ti! esta web me ha dado casi todo lo que se de pygame así que compartir este ejemplo es lo mínimo que puedo hacer. Y me alegro de haber ayudado a que crezca un poco la web que por cierto... me fijé en los autores y me dí cuenta de que muchos de los ejemplos son tuyos, valoro mucho tu esfuerzo, y aprovecho para animarte a continuar porque realmente son de gran ayuda ;) y a ver si se suma mas gente...

Saludos.
Avatar de Usuario
endaramiz
 
Mensajes: 283
Registrado: Vie Ago 31, 2007 9:25 am
Ubicación: Barcelona


Volver a Artículos, traducciones y documentación

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 2 invitados

cron