Maquina de Estados Finitos




Imaginemos un pequeño robot que recogerá diez objetos colocados al azar. Si en algún momento el robot empieza a quedarse sin batería, tendrá que ir a recoger una nueva. Si el robot se queda por completo sin batería, se muere.
Observamos los estados necesarios:

- Estado de búsqueda en el cual el robot se dirija hacia el objeto.
- Estado en el que decida cuál es el siguiente objeto a recoger.
- Estado en el cual el robot deberá ir a por una batería nueva.
- Estado en que llega a la batería y recarga energía.
- Estado en el que el robot muere por falta de energía.
- Estado en el que el robot se mueve al azar ya que no hay mas objetos que recoger.

Transiciones:

Si se encuentra en el estado de búsqueda, puede seleccionar un nuevo objeto que buscar si ya llegó al objeto buscado, o dirigirse hacia la batería si su cantidad de energía es menor a 400 unidades.
Si se encuentra en el estado en el que escoge el siguiente objeto por buscar, puede cambiar de estado. Se cambia a búsqueda cuando haya seleccionado el nuevo objeto. Si no hay más objetos para recoger, entonces se pasa al estado de movimiento aleatorio.
En el estado aleatorio, se lleva a cabo la transición hacia el esta de muerto cuando la energía en la batería llega a cero. El estado de muerto no tiene transición a ningún otro estado.
Cuando se dirige a la batería y la energía de ésta llega al valor cero, entonces también la transición es hacia el estado de muerto. Si llega a la batería antes de que esto suceda, se pasa al estado donde recarga. En el estado de recargar la batería, hay una transición hacia el estado de búsqueda, para completar el ciclo de estados finitos de nuestro robot.

Enlace de descarga del proyecto completo:

https://code.msdn.microsoft.com/Maquina-de-Estados-Finitos-94da3a96


using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace MEF
{
   
    public class Form1 : Form
    {
        private MainMenu mainMenu1;
        private MenuItem menuItem1;
        private MenuItem mnuSalir;
        private MenuItem menuItem3;
        private MenuItem mnuInicio;
        private MenuItem mnuParo;
        private Timer timer1;
        private IContainer components;

        // Creamos un objeto para la maquina de estados finitos
        private CMaquina maquina = new CMaquina();

        // Objetos necesarios
        public S_objeto[] ListaObjetos = new S_objeto[10];
        public S_objeto MiBateria;

        public Form1()
        {
            //
            // Necesario para admitir el Diseñador de Windows Forms
            //
            InitializeComponent();
                       
            //

            // Inicializamos los objetos

            // Cremos un objeto para tener valores aleatorios
            Random random = new Random();

            // Recorremos todos los objetos
            for (int n = 0; n < 10; n++)
            {
                // Colocamos las coordenadas
                ListaObjetos[n].x = random.Next(0, 639);
                ListaObjetos[n].y = random.Next(0, 479);

                // Lo indicamos activo
                ListaObjetos[n].activo = true;
            }

            // Colocamos la bateria
            MiBateria.x = random.Next(0, 639);
            MiBateria.y = random.Next(0, 479);
            MiBateria.activo = true;

            maquina.Inicializa(ref ListaObjetos, MiBateria);

        }
   
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }
     
        static void Main()
        {
            Application.Run(new Form1());
        }

        private void mnuSalir_Click(object sender, System.EventArgs e)
        {
            // Cerramos la ventana y finalizamos la aplicacion
            this.Close();
        }

        private void mnuInicio_Click(object sender, System.EventArgs e)
        {
            timer1.Enabled = true;
        }

        private void mnuParo_Click(object sender, System.EventArgs e)
        {
            timer1.Enabled = false;
        }

        private void timer1_Tick(object sender, System.EventArgs e)
        {
            // Esta funcion es el handler del timer
            // Aqui tendremos la logica para actualizar nuestra maquina de estados

            // Actualizamos a la maquina
            maquina.Control();

            // Mandamos a redibujar la pantalla
            this.Invalidate();
        }

        private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
        {
            // Creamos la fuente y la brocha para el texto
            Font fuente = new Font("Arial", 16);
            SolidBrush brocha = new SolidBrush(Color.Black);

            // Dibujamos el robot
            if (maquina.EstadoM == (int)CMaquina.estados.MUERTO)
                e.Graphics.DrawRectangle(Pens.Black, maquina.CoordX - 4, maquina.CoordY - 4, 20, 20);
            else
                e.Graphics.DrawRectangle(Pens.Green, maquina.CoordX - 4, maquina.CoordY - 4, 20, 20);

            // Dibujamos los objetos
            for (int n = 0; n < 10; n++)
                if (ListaObjetos[n].activo == true)
                    e.Graphics.DrawRectangle(Pens.Indigo, ListaObjetos[n].x - 4, ListaObjetos[n].y - 4, 20, 20);

            // Dibujamos la bateria
            e.Graphics.DrawRectangle(Pens.IndianRed, MiBateria.x - 4, MiBateria.y - 4, 20, 20);

            // Indicamos el estado en que se encuentra la maquina
            e.Graphics.DrawString("Estado -> " + maquina.EstadoM.ToString(), fuente, brocha, 10, 10);

        }
    }
}


using System;

namespace MEF
{
// Estructura usada para los objetos y la bateria
public struct S_objeto
{
public bool activo; // Indica si el objeto es visible o no
public int x,y; // Coordenadas del objeto
}


/// <summary>
/// Esta clase representa a nuestra maquina de estados finitos.
/// </summary>
public class CMaquina
{
// Enumeracion de los diferentes estados
public enum  estados
{
BUSQUEDA,
NBUSQUEDA,
IRBATERIA,
RECARGAR,
MUERTO,
ALEATORIO,
};

// Esta variable representa el estado actual de la maquina
private int Estado;

// Estas variables son las coordenadas del robot
private int x,y;

// Arreglo para guardar una copia de los objetos
private S_objeto[] objetos = new S_objeto[10];
private S_objeto bateria;

// Variable del indice del objeto que buscamos
private int indice;

// Variable para la energia;
private int energia;

// Creamos las propiedades necesarias
public int CoordX 
{
get {return x;}
}

public int CoordY
{
get {return y;}
}

public int EstadoM
{
get {return Estado;}
}
public CMaquina()
{
// Este es el contructor de la clase

// Inicializamos las variables

Estado=(int)estados.NBUSQUEDA; // Colocamos el estado de inicio.
x=320; // Coordenada X
y=240; // Coordenada Y
indice=-1; // Empezamos como si no hubiera objeto a buscar
energia=800;
}

public void Inicializa(ref S_objeto [] Pobjetos, S_objeto Pbateria)
{
// Colocamos una copia de los objetos y la bateria
// para pode trabajar internamente con la informacion

objetos=Pobjetos;
bateria=Pbateria;

}

public void Control()
{
// Esta funcion controla la logica principal de la maquina de estados
switch(Estado)
{
case (int)estados.BUSQUEDA:
// Llevamos a cabo la accion del estado
Busqueda();

// Verificamos por transicion
if(x==objetos[indice].x && y==objetos[indice].y)
{
// Desactivamos el objeto encontrado
objetos[indice].activo=false;
// Cambiamos de estado
Estado=(int)estados.NBUSQUEDA;

}
else if(energia<400) // Checamos condicion de transicion
Estado=(int)estados.IRBATERIA;

break;

case (int)estados.NBUSQUEDA:
// Llevamos a cabo la accion del estado
NuevaBusqueda();

// Verificamos por transicion
if(indice==-1) // Si ya no hay objetos, entonces aleatorio
Estado=(int)estados.ALEATORIO;
else
Estado=(int)estados.BUSQUEDA;

break;
case (int)estados.IRBATERIA:
// Llevamos a cabo la accion del estado
IrBateria();

// Verificamos por transicion
if(x==bateria.x && y==bateria.y)
Estado=(int)estados.RECARGAR;

if(energia==0)
Estado=(int)estados.MUERTO;

break;

case (int)estados.RECARGAR:
// Llevamos a cabo la accion del estado
Recargar();

// Hacemos la transicion
Estado=(int)estados.BUSQUEDA;

break;

case (int)estados.MUERTO:
// Llevamos a cabo la accion del estado
Muerto();

// No hay condicion de transicion
break;

case (int)estados.ALEATORIO:
// Llevamos a cabo la accion del estado
Aleatorio();

// Verificamos por transicion
if(energia==0)
Estado=(int)estados.MUERTO;

break;
}
}

public void Busqueda()
{
// En esta funcion colocamos la logica del estado Busqueda
// Nos dirigimos hacia el objeto actual
if(x<objetos[indice].x)
x++;
else if(x>objetos[indice].x)
x--;

if(y<objetos[indice].y)
y++;
else if(y>objetos[indice].y)
y--;

// Disminuimos la energia
energia--;

}

public void NuevaBusqueda()
{
// En esta funcion colocamos la logica del estado Nueva Busqueda
// Verificamos que haya otro objeto a buscar
indice=-1;

// Recorremos el arreglo buscando algun objeto activo
for(int n=0;n<10;n++)
{
if(objetos[n].activo==true)
indice=n;
}
}

public void Aleatorio()
{
// En esta funcion colocamos la logica del estado Aleatorio
// Se mueve al azar

// Cremos un objeto para tener valores aleatorios
Random random=new Random();

int nx=random.Next(0,3);
int ny=random.Next(0,3);

// Modificamos la posicion al azar
x+=nx-1;
y+=ny-1;

energia--;

}

public void IrBateria()
{
// En esta funcion colocamos la logica del estado Ir Bateria

// Nos dirigimos hacia la bateria
if(x<bateria.x)
x++;
else if(x>bateria.x)
x--;

if(y<bateria.y)
y++;
else if(y>bateria.y)
y--;

// Disminuimos la energia
energia--;

}

public void Recargar()
{
// En esta funcion colocamos la logica del estado Recargar
energia=1000;

}

public void Muerto()
{
// En esta funcion colocamos la logica del estado Muerto

// Sonamos un beep de la computadora
}
}
}
// Código original de Nicolás Arrioja. Inteligencia Artificial en C# ISBN 978-987-1347-51-2

No hay comentarios:

Publicar un comentario

Nota: solo los miembros de este blog pueden publicar comentarios.