jueves, 10 de noviembre de 2011

Buscaminas inteligente

Es la culminación del programa hecho en C# que juega solo al buscaminas, ya que logré que gane en 1 segundo en el modo experto.

Ahora paso a explicar los puntos importantes que permitieron aumentar el rendimiento.

Casi todo el cambio lo apliqué a la capa de reconocimiento, ya que es la encargada de traer las imágenes y eso es lo pesado del proceso. Lo primero que hice es que en lugar de leer pixel por pixel de la pantalla (un proceso muy lento) que lea toda una línea y luego que vaya leyendo esa línea.

ReconocimientoDePantalla.CreaBitmapRecorte(0, 0, ancho, 1);
Bitmap bmpLineaHorizontal = ReconocimientoDePantalla.ObtieneRecorteDePantalla(0, y_linea);


A la clase estática ReconocimientoDePantalla le agregué el siguiente código:

private static Bitmap bitmapRecorte;

        public static void CreaBitmapRecorte(int x1, int y1, int x2, int y2)
        {
            bitmapRecorte = new Bitmap(x2 - x1, y2 - y1, PixelFormat.Format32bppPArgb);
        }

        public static Bitmap ObtieneRecorteDePantalla(int x1, int y1)
        {
            using (Graphics gdest = Graphics.FromImage(bitmapRecorte))
            {
                using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero))
                {
                    IntPtr hSrcDC = gsrc.GetHdc();
                    IntPtr hDC = gdest.GetHdc();
                    int retval = BitBlt(hDC, 0, 0, bitmapRecorte.Width, bitmapRecorte.Height, hSrcDC, x1, y1, (int)CopyPixelOperation.SourceCopy);
                    gdest.ReleaseHdc();
                    gsrc.ReleaseHdc();
                }
            }
            return bitmapRecorte;
        }

Y en la clase BuscaMinasDeCarga le cambié el método siguiente:


private bool BusquedaDeCuadro(ref int ini_x, ref int ini_y, ref int nCuadrosX, ref int nCuadrosY)
        {
            // Para el ancho de la pantalla
            int ancho = Screen.PrimaryScreen.Bounds.Width;
            // Para el alto de la pantalla
            int alto = Screen.PrimaryScreen.Bounds.Height;
            int incrementoY = alto / lineasHorizontales;
            int inicioY = 150;
            int ladoX = 0;
            int ladoY = 0;

            for (int y_linea = inicioY; y_linea < alto; y_linea += incrementoY)
            {
                int indice = 0;
                bool encuentraX = false;
                bool encuentraY = false;
                int x_encontrado = 0;
                int y_encontrado = 0;
                ReconocimientoDePantalla.CreaBitmapRecorte(0, 0, ancho, 1);
                Bitmap bmpLineaHorizontal = ReconocimientoDePantalla.ObtieneRecorteDePantalla(0, y_linea);
                for (int x = 0; x < ancho; x++)
                {
                    //int color = ReconocimientoDePantalla.LeeUnPixelDePantalla(x, y_linea);
                    int color = bmpLineaHorizontal.GetPixel(x, 0).ToArgb();
                    if (coloresIniciales[indice] == color)
                    {
                        indice++;
                        if (indice >= coloresIniciales.Length)
                        {
                            encuentraX = true;
                            x_encontrado = x;
                            break;
                        }
                    }
                    else
                    {
                        indice = 0;
                    }
                }
                if (encuentraX)
                {
                    y_encontrado = y_linea;
                    ReconocimientoDePantalla.CreaBitmapRecorte(0, 0, 1, alto);
                    Bitmap bmpLineaVertical = ReconocimientoDePantalla.ObtieneRecorteDePantalla(x_encontrado, 0);
                    for (int y = y_linea; y >= 0; y--)
                    {
                        //int color = ReconocimientoDePantalla.LeeUnPixelDePantalla(x_encontrado, y);
                        int color = bmpLineaVertical.GetPixel(0, y).ToArgb();
                        if (color == colorRodea)
                        {
                            y_encontrado = y;
                        }
                        else
                        {
                            if (color == colorTope)
                            {
                                encuentraY = true;
                            }
                            break;
                        }
                    }
                    if (encuentraY)
                    {
                        for (int i = y_encontrado; i < alto; i++)
                        {
                            //int color = ReconocimientoDePantalla.LeeUnPixelDePantalla(x_encontrado, i);
                            int color = bmpLineaVertical.GetPixel(0, i).ToArgb();
                            if (color != colorRodea)
                            {
                                ladoY = i - y_encontrado;
                                break;
                            }
                        }
                        ReconocimientoDePantalla.CreaBitmapRecorte(0, 0, ancho, 1);
                        bmpLineaHorizontal = ReconocimientoDePantalla.ObtieneRecorteDePantalla(0, y_encontrado);
                        for (int i = x_encontrado; i < ancho; i++)
                        {
                            //int color = ReconocimientoDePantalla.LeeUnPixelDePantalla(i, y_encontrado);
                            int color = bmpLineaHorizontal.GetPixel(i, 0).ToArgb();
                            if (color != colorRodea)
                            {
                                ladoX = i - x_encontrado;
                                break;
                            }
                        }
                    }
                }
                if (encuentraX && encuentraY)
                {
                    int n1 = ladoX - bordeX;
                    int n2 = ladoY - BordeY;
                    if ((n1 % ladoDelCuadroX) != 0)
                    {
                        continue;
                    }
                    if ((n2 % ladoDelCuadroY) != 0)
                    {
                        continue;
                    }
                    ini_x = x_encontrado;
                    ini_y = y_encontrado;
                    nCuadrosX = n1 / ladoDelCuadroX;
                    nCuadrosY = n2 / ladoDelCuadroY;
                    if ((nCuadrosX < 1) || nCuadrosY < 1)
                    {
                        continue;
                    }
                    return true;
                }
            }
            return false;
        }

También modifiqué el método Reconocimiento, para que lea un cuadro de la pantalla un poco más rápido


public void Reconocimiento()
        {
            if (actualizando) { return; }
            actualizando = true;
            int x = 0, y = 0, nX = 0, nY = 0;
            if (BusquedaDeCuadro(ref x, ref y, ref nX, ref nY))
            {
                posIniDePantallaX = x;
                posIniDePantallaY = y;
                if ((matriz.GetLength(0) != nX) || (matriz.GetLength(1) != nY))
                {
                    InicializaMatriz(nX, nY);
                }
                ReconocimientoDePantalla.CreaBitmapRecorte(x, y, x + (nX * ladoDelCuadroX) + bordeX, y + (nY * ladoDelCuadroY) + bordeY);
                Bitmap bmp = ReconocimientoDePantalla.ObtieneRecorteDePantalla(x, y);

                //Bitmap bmp = ReconocimientoDePantalla.ObtieneRecorteDePantalla(x, y, x + (nX * ladoDelCuadroX) + bordeX, y + (nY * ladoDelCuadroY) + bordeY);
                byte[, ,] imagen = ImagenConverterBMP.byteArrayToMatrix(ImagenConverterBMP.imageToByteArray(bmp));
                CargaMatrizDeElementos(imagen, posIniXdeCuadro, posIniYdeCuadro, anchoElemento, altoElemento, ladoDelCuadroX, ladoDelCuadroY);
                encuentraCuadro = true;
            }
            else
            {
                encuentraCuadro = false;
            }

            actualizando = false;
        }

Otra de las modificaciones sobre la capa de reconocimiento es que en lugar de comparar todo cuadro correspondiente al elemento, hice que compare solo algunos puntos, con eso es suficiente como para identificarlos, aunque al final no afecta demasiado el rendimiento pero suma.


private TipoDeElemento BuscaElemento(byte[, ,] mat)
        {
            for (int i = 0; i < lImagenesDeElementos.Count; i++)
            {
                byte[, ,] mat1 = lImagenesDeElementos[i];
                bool q = false;
                for (int y = 0; y < mat.GetLength(1); y+=3)
                {
                    for (int x = 0; x < mat.GetLength(0); x+=3)
                    {
                        for (int j = 0; j < 3; j++)
                        {
                            if (mat[x, y, j] != mat1[x, y, j])
                            {
                                q = true;
                                break;
                            }
                        }
                        if (q) break;
                    }
                    if (q) break;
                }
                if (!q)
                {
                    int k = 0;
                    foreach (TipoDeElemento te in Enum.GetValues(typeof(TipoDeElemento)))
                    {
                        if (k == i)
                        {
                            return te;
                        }
                        k++;
                    }
                    return TipoDeElemento.error;
                }
            }
            return TipoDeElemento.error;
        }


Las modificaciones sobre la capa de negocio son mínimas, sin embargo tienen un efecto muy positivo, en definitiva hago que en lugar de realizar una jugada por reconocimiento, haga todas las que pueda, para eso nada más tengo que quitar el retorno cuando encuentra la jugada, lo mismo para el método avanzado.


private bool BuscaVacio()
        {
            bool encuentra = false;
            for (int i = lDeIncognitas.Count - 1; i >= 0; i--)
            {
                ElementoBM ebm = lDeIncognitas[i];
                foreach (ElementoBM el in ebm.EVecinos)
                {
                    if (el != null)
                    {
                        if (el.EsNumero)
                        {
                            if (DeterminaSiEsVacio(el)) 
                            {
                                if (realizaJugadaMultiple)
                                {
                                    encuentra = true;
                                }
                                else
                                {
                                    return true; 
                                }
                            }
                        }
                    }
                }
            }
            return encuentra;
        }






No hay comentarios:

Publicar un comentario en la entrada