El hormiguero – Feromonas

Posted on mayo 3, 2010

0


Hasta el momento hemos logrado que nuestras hormigas se muevan de manera aleatoria. Ha llegado la hora de que hagan algo que deben hacer las hormigas: formar hileras.

Para esto nos vamos a valer del concepto de feromonas. Tal como les contaba en el primer artículo de esta serie, las hormigas se guían por un rastro químico que ellas mismas van dejando. Nosotros implementaremos eso mismo. Cada vez que una hormiga dé un paso, provocaremos que deje este rastro. El rastro debe poder incrementar otro rastro que haya sido dejado con anterioridad, para esto utilizaremos una variable que simule la intensidad del rastro.

Cuando una hormiga encuentre un rastro, deberá seguir su dirección, cuando no lo encuentre, seguirá paseando aleatoriamente. Este será, a grandes rasgos, el comportamiento que buscaremos con la clase feromona, la cual muestro a continuación:

    public class CFeromona
    {
        private float mIntensidad;
        public CCircle Posicion { get; set; }
        public float Direccion;
        public float Intensidad
        {
            get { return mIntensidad; }
            set
            {
                mIntensidad=value;
                if (mIntensidad > 255)
                    mIntensidad = 255;
            }
        }

        public CFeromona()
        {
            Intensidad = 10;
            Posicion = new CCircle();
            Posicion.Radius = 5;
        }

        public void Render(Graphics g)
        {
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            g.ResetTransform();
            g.TranslateTransform(Posicion.Center.X, Posicion.Center.Y);
            Brush br = new SolidBrush(Color.FromArgb((int)Intensidad, 220, 220, 255));
            g.FillEllipse(br,
                new Rectangle(
                    new Point(-Posicion.Radius, -Posicion.Radius),
                    new Size(Posicion.Radius * 2, Posicion.Radius * 2)));
        }
    }

Como pueden observar, la posición de una feromona está definida como un círculo. Esto lo hago para facilitar el que una hormiga pueda encontrarla, si fuera un punto, tendríamos que ponernos a esperar todo el día. Por este mismo motivo, he modificado la clase hormiga y ahora es como sigue:

    public class CHormiga
    {
        public CCircle Posicion { get; set; }
        public float Direccion { get; set; }
        public int Estado { get; set; }
        public int Velocidad { get; set; }

        public CHormiga()
        {
            Estado = 0;
            Posicion = new CCircle();
            Posicion.Radius = 8;
        }

        public void Render(Graphics g)
        {
            Posicion.Render(g);
            Point p = Posicion.Center;
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            g.ResetTransform();
            g.TranslateTransform(p.X, p.Y);
            g.RotateTransform(Direccion);
            Pen pen = new Pen(Color.Black);
            g.FillEllipse(Brushes.Black, new Rectangle(new Point(-2, -10), new Size(4, 6)));
            g.FillEllipse(Brushes.Black, new Rectangle(new Point(-1, -4), new Size(2, 2)));
            g.FillEllipse(Brushes.Black, new Rectangle(new Point(-2, -2), new Size(4, 4)));
            g.DrawLine(pen, 0, 2, -3, 5);
            g.DrawLine(pen, 0, 2, 3, 5);
            pen.Dispose();
        }
    }

Y bueno, ya se estarán preguntando cómo es esta clase CCirculo. Pues no se preocupen que aquí va:

    public class CCircle
    {
        public Point Center { get; set; }
        public int Radius { get; set; }

        public void Render(Graphics g)
        {
            g.ResetTransform();
            g.TranslateTransform(Center.X, Center.Y);
            g.DrawEllipse(new Pen(Brushes.Salmon), -Radius / 2, -Radius / 2, Radius, Radius);
        }
    }

Pero los cambios más radicales están en la clase Controladora. Ya no basta con mover a las hormigas, ahora es necesario actualizar la intensidad de las feromonas para simular su evaporación y también es necesario verificar si cada hormiga se ha topado con un rastro de feromonas.

Esta sería la nueva versión de la clase controladora:

    class CHormiguero
    {
        private double counter = 0;
        private Point mLimits;

        public ArrayList Hormigas { get; set; }
        public ArrayList Feromonas { get; set; }
        public Point Posicion { get; set; }
        public Point Limits
        {
            get { return mLimits; }
            set
            {
                mLimits = value;
                foreach (CHormiga hormiga in Hormigas)
                {
                    if (hormiga.Posicion.Center.X > mLimits.X)
                    {
                        hormiga.Posicion.Center =
                            new Point(mLimits.X, hormiga.Posicion.Center.Y);
                    }
                    if (hormiga.Posicion.Center.Y > mLimits.Y)
                    {
                        hormiga.Posicion.Center =
                            new Point(hormiga.Posicion.Center.X, mLimits.Y);
                    }
                }
            }
        }

        public CHormiguero(int numHormigas, int width, int height, int velocidad)
        {
            Hormigas = new ArrayList();
            Feromonas = new ArrayList();
            Limits = new Point(width, height);
            Posicion = new Point(width/2, height/2);

            Random rnd = new Random();
            for (int i = 0; i < numHormigas; i++)
            {
                CHormiga hormiga = new CHormiga();
                hormiga.Posicion.Center =
                    new Point(rnd.Next(width), rnd.Next(height));
                hormiga.Direccion = ((float)rnd.NextDouble() * 360) - 180;
                hormiga.Velocidad = rnd.Next(velocidad - 5) + 5;
                Hormigas.Add(hormiga);
            }
        }

        public void Render(Graphics g)
        {
            //g.FillEllipse(Brushes.Gray, Posicion.X - 5, Posicion.Y - 5, 10, 10);
            foreach (CFeromona feromona in Feromonas)
            {
                feromona.Render(g);
            }
            foreach (CHormiga hormiga in Hormigas)
            {
                hormiga.Render(g);
            }
        }

        public void ActualizarFeromonas(float tiempo)
        {
            for (int i = Feromonas.Count - 1; i >= 0; i--)
            {
                CFeromona feromona = (CFeromona)Feromonas[i];
                feromona.Intensidad -= tiempo/2;
                if (feromona.Intensidad < 1)
                {
                    Feromonas.Remove(feromona);
                }
            }
        }

        public void MoverHormigas(float tiempo)
        {
            Random rnd = new Random();
            counter += 1;
            foreach (CHormiga hormiga in Hormigas)
            {
                float d = hormiga.Direccion;
                float radd = (float)((d * Math.PI) / 180);
                int dx = -(int)(Math.Sin(radd) * hormiga.Velocidad * tiempo);
                int dy = (int)(Math.Cos(radd) * hormiga.Velocidad * tiempo);
                Point p = hormiga.Posicion.Center;
                p.X += dx;
                p.Y += dy;
                //Detectar feromonas
                CFeromona bestF = null;
                //Si detecta feromona, ir en esa direccion
                //Si no detecta, tomar un camino al azar y dejar feromona
                if (DetectarFeromona(hormiga, ref bestF))
                {
                    bestF.Intensidad += 10;
                    d = bestF.Direccion;
                }
                else
                {
                    d += ((float)rnd.NextDouble() * 60) - 30;
                    if (Feromonas.Count < 1000)
                    {
                        CFeromona f = new CFeromona();
                        f.Posicion.Center = hormiga.Posicion.Center;
                        f.Direccion = d;
                        Feromonas.Add(f);
                    }
                }
                if ((p.X > 0) && (p.X < Limits.X) &&
                    (p.Y > 0) && (p.Y < Limits.Y))
                {
                }
                else
                {
                    if (p.X < 0) p.X = Limits.X;
                    if (p.X > Limits.X) p.X = 0;
                    if (p.Y < 0) p.Y = Limits.Y;
                    if (p.Y > Limits.Y) p.Y = 0;
                }
                hormiga.Posicion.Center = p;
                hormiga.Direccion = d;
            }
        }

        private bool DetectarFeromona(CHormiga hormiga, ref CFeromona bestF)
        {
            bool hit = false;
            foreach (CFeromona feromona in Feromonas)
            {
                {
                    if (CColisiones.Colision(feromona.Posicion, hormiga.Posicion))
                    {
                        if (!hit)
                        {
                            bestF = feromona;
                            hit = true;
                        }
                        else
                        {
                            if (feromona.Intensidad > bestF.Intensidad)
                            {
                                bestF = feromona;
                            }
                        }
                    }
                }
            }
            return hit;
        }
    }

También se ha creado la siguiente clase que verifica las colisiones de forma bastante sencilla, y esta era una de las razones de hacer que la posición de las hormigas y las feromonas sea definida como un círculo:

    public static class CColisiones
    {
        public static bool Colision(CCircle c1, CCircle c2)
        {
            double r = c1.Radius + c2.Radius;
            double x = c2.Center.X - c1.Center.X;
            double y = c2.Center.Y - c1.Center.Y;
            return (r * r > (x * x + y * y));
        }

Y ya tenemos todo listo para utilizarlo en un formulario que tenga su respectivo Timer.

El resultado que yo he obtenido es el siguiente:

El formulario mostrado tiene sólo 15 hormigas, pero lo he probado con 300 y funciona igualmente bien: las hormigas van dejando un rastro que es seguido por las demás y mientras más hormigas lo recorren, este se hace más nítido.

Lo más importante, por ahora, es que se confirma que las hormigas sólo necesitan del rastro de feromonas para funcionar organizadamente.

En un siguiente artículo jugaremos un poco con las probabilidades de seguir o abandonar un determinado rastro y veremos si todo esto es suficiente para encontrar comida y llevarla de vuelta al nido.

No duden en dejarme un comentario si es que tienen alguna duda, pues aquí se han encontrado muchos conceptos diferentes: manejo de gráficos, colisiones, algo de matemática, además del tema central que son las hormigas. Por ese motivo he pasado por alto algunos conceptos, pero eso no significa que no esté dispuesto a comentarlos si que algún lector desea aclarar algún punto.

¡Hasta pronto!

        public CHormiguero(int numHormigas, int width, int height)
        {
            Hormigas = new ArrayList();
            Velocidad = 10;
            Limits = new Point(width, height);

            Random rnd = new Random();
            for (int i = 0; i < numHormigas; i++)
            {
                CHormiga hormiga = new CHormiga();
                hormiga.Posicion =
                    new Point(rnd.Next(width), rnd.Next(height));
                hormiga.Direccion =
                    ((float)rnd.NextDouble() * 360) - 180;
                Hormigas.Add(hormiga);
            }
        }

Advertisement
Posted in: CSharp, dotNET, IA