Está en la página 1de 19

intec. elementos (ing-202). ecabrera. mayo 2010.

minesWeeper, ejemplo 5

Juego MinesWeeper (busca minas) Hay un juego de lgica que vino con Windows 95, llamado MinesWeeper (busca minas) cuyo objetivo es encontrar, sin pisar ninguna, las minas que hay en un campo minado usando la ayuda que consiste en mostrar en las celdas prximas a donde se pisa, la cantidad de minas que hay alrededor de ellas. Empecemos creando un Windows Forms Application, cambiando el ttulo del Form (propiedad Text) a MinesWeeper, colocando un botn sobre el Form cerca de su esquina superior izquierda, haciendo su propiedad Size igual a 30,30, borrando el valor de su propiedad Text (equivale a una cadena nula), sacndole 9 copias disponindolas en una fila.

Verificamos que la propiedad Tag de cada botn est en blanco, pero que la propiedad TabIndex sigue el orden 0, 1, 2, , 8, 9. Por tanto, podemos, podemos usar la secuencia que natural que sigue esta propiedad para individualizar cada botn en vez de usar, asignndola a mano, la propiedad Tag, como hicimos en el ejemplo Tic Tac Toe. Verifiquemos que podemos usar esta propiedad pulsando doble el primer botn (TabIndex 0) evento Click que aparece, en el modo de cdigo, copiamos el siguiente cdigo: foreach (System.Windows.Forms.Control ctrl in this.Controls) ctrl.Text = (Convert.ToString(ctrl.TabIndex)); Esto recorre cada uno de los, hasta ahora, 10 botones que hemos colocado en el Form, toma el valor de su propiedad TabIndex, lo convierte a string y se lo asigna a la propiedad Text del control (en este caso botn). Si ejecutamos la aplicacin y pulsamos el primer botn veremos como los 10 botones muestran los dgitos del 0 al 9, como esperbamos. Esto nos garantiza que, realmente, la propiedad TabIndex, que es colocada automticamente a cada botn por Visual C# la podemos usar ms adelante para acceder a cada botn individualmente, mediante el valor de su propiedad TabIndex, que tan generosamente nos auto-numera Visual C#. Ahora le seleccionamos los diez botones y le sacamos una copia, de manera que obtengamos 20, y la colocamos prximo a la primera.

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

Verificamos que, efectivamente, los valores TabIndex de los nuevos botones van de 10 a 19. Por lo que ahora, podemos seleccionar los 20 botones y sacarle una copia, obteniendo 40. Y estos cuarenta le sacamos una copia, obteniendo 80, y, finalmente, selecionamos las ltimas 20 copias y le sacamos una copia, para completar un total de 100 botones, con sus TabIndex que van de 0 a 99. Note que hay que redimensionar el Form para albergar cmodamente a los 100 botones.

Ahora necesitamos crear una matriz, de mbito global, para colocar las minas, de manera aleatoria, y determinar cuntas minas hay alrededor de cada celda. Copiamos el siguiente texto inmediatamente despus de la llave, {, que inicia el cuerpo de la clase parcial (partial class). int[] mt = new int[100]; // matriz de estado

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

Ahora redactamos un mtodo que genere las minas y las coloque al azar sobre el trasfondo del tablero de botones. Este mtodo lo insertamos inmediatamente despus del mtodo Click. private void minas() { } Note que este mtodo luego de la directriz private tiene el tipo de dato void, sin valor -que no es, en realidad, un tipo de dato- porque NO retorna valor, y por la misma razn, que no recibe valor, es que entre los parntesis no hay nada. Este mtodo, pues, no recibe ni retorna nada, simplemente realiza una accin silenciosa, a saber, colocar ciertos valores en la matriz mt, pero como esta es de mbito global, no hay que pasarla al mtodo como parmetro. Lo primero que hace el mtodo es asegurar que todas las posiciones en la matriz tengan los nmeros enteros no negativos desde cero a 99, para poder barajar todos estos valores. private void minas() { for (int k = 0; k < 100; k++) mt[k] = k; } Para generar las minas al azar, declararemos una variable aleatoria de mbito global, az, desde la cual invocaremos el mtodo Next para obtener los nmeros aleatorios que neceitamos. Esto hacemos debajo de la lnea en que declaramos la matriz, mt. Random az = new Random(); // variable aleatoria

En el mtodo minas declaramos tres variables enteras; una para la posicin aleatoria, a; otra para apuntar a la ltima posicin actual, u; y otra para intercambiar los valores en la matriz mt que estn en las posiciones a y u, t.

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

private void minas() { int a, u, t; // posicin recorrido, posicin, temporal for (int k = 0; k < 100; k++) mt[k] = k; } Ahora barajamos los 100 valores colocados en la mt. Para ello redactamos un ciclo for con la variable contador u, la cual toma todos los valores enteros entre 99 y 1, de manera regresiva, y para cada valor de u generamos un valor aleatorio, a, invocando el mtodo Next de la variable aleatoria az. Entonces copiamos el valor en matriz mt que est en la posicin a variable t, copiamos el que est en la posicin u de mt a la posicin a de mt, y finalmente copiamos el valor de t ala posicin u de mt. El cdigo para esto se inserta en el mtodo minas a seguidas del ciclo for anterior. Note que el valor de a es siempre menor que u. for (u = 99; u > 0; u--) { a = az.Next(u); t = mt[a]; mt[a] = mt[u]; mt[u] = t; }

Verifiquemos los valores han sido barajados cambiando el cdigo del evento Click (botn 1). minas(); foreach (System.Windows.Forms.Control ctrl in this.Controls) ctrl.Text = (Convert.ToString(mt[ctrl.TabIndex]));

Ejecutamos el programa y comprobamos que, cada vez que pulsamos el primer botn, los nmeros en los 100 botones se muestran los nmeros del 0 a l 99, pero en distinto orden, pues cada vez que se ejecuta el mtodo minas se barajan nuevamente las posiciones de los botones en la matriz de estado.

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

Si queremos, pues, slo 25 minas distribuidas al azar, podemos tomar los nmeros aleatorios que queden en las primeras 25 posiciones de la matriz de estado, aunque serviran igual las ltimas 25, o cualquier otro segmento. Por tanto, si copiamos los 25 valores de la matriz de estado en un arreglo de 25 posiciones, hacemos cero todos los elementos de la matriz de estado y colocamos un 9 en las posiciones de la matriz de estado que indican los valores en la matriz de 25 valores, equivale a colocar 25 minas al azar en nuestro campo minado. Empecemos por declarar, a nivel global la matriz de 25 valores (debajo de la variable aleatoria az). int[] mn = new int[25]; // posiciones de las minas

Ahora copiamos las primeras 25 posiciones de la matriz de estado a la matriz de posiones de las minas, hacemos cero todas las posiciones de la matriz de estado y hacemos igual a 9 las 25 posiciones indicadas en las posiciones de las minas. Este es el cdigo, hasta ahora, del mtodo minas.

int a, u, t; // posicin recorrido, posicin, temporal for (int k = 0; k < 100; k++) mt[k] = k; for (u = 99; u > 0; u--) { a = az.Next(u); t = mt[a]; mt[a] = mt[u]; mt[u] = t; } for (int k = 0; k < 25; k++) mn[k] = mt[k]; for (int k = 0; k < 100; k++) mt[k] = 0; for (int k = 0; k < 25; k++) mt[mn[k]] = 9; Ejecutamos la palicacin y comprobamos que cada vez que pulsamos el prime botn, se despliegan 25 nueves en los botones y el resto son ceros. Cada vez que se pulsa el botn, las posiciones de los 9s cambia de manera aleatoria, como debe ser. Podramos jugar a la lotera con esto!

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

Puede que el lector se pregunte porqu pusimos un nueve para indicar que hay una mina en una determinada celda. La razn es que la vecindad de cada celda est compuesta por un mximo de 8 celdas. 1 8 7 2 x 6 3 4 5

Por tanto, en la matriz de estado podemos usar los nmeros de 0 a 8 para indicar el nmero de minas que hay alrededor de una celda dada, y el 9 para indicar que en ella misma hay una mina. Debemos, ya que hemos logrado minar el campo, determinar, para las celdas en que no hay minas, determinar cuntas minas hay a su alrededor. Lo primero es recordar que en nuestro caso el campo minado es de 10x10 celdas, esto es, un total de 100 celdas, que hemos numerado de 0 a 99. Se recordar del ejercicio Tic Tac Toe (cerito-cruz), que las celdas estaban numeradas de 0 a 8, pero como para el usuario era un matriz de 3x3, usamos dos funciones sencillas para obtener, dada la posicin lineal de la celda (Tag), la fila y la columna de la celda, segn la vea el usurio. f = c = p / 3; p % 3;

Donde p es la posicin lineal, f la fila y c la columna. Usaremos el mismo truco esta vez para obtener dada la posicin lineal de la celda (TabIndex del botn) la fila y la columna, usando las mismas frmulas pero cambiando el 3 por un 10, porque ahora nuestro tablero es de 10x10. f = p / 10; c = p % 10; Para determinar el nmero de minas que hay alrededor de una celda (si en ella no hay mina, naturalmente) tenemos que recorrer la vecindad de la celda, que, salvo para las celdas que estn en los bordes (fila 0, colunma 9, fila 9 y columna 0) que tiene 8 celdas.

1 ->

8 7

f,c 6

4 5

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

Note que hemos colocado una flecha en la celda que est en la esquina superior izquierda de la vecindad de la celda (f,c) para indicar el sentido que adoptamos para recorrer la vecindad en busca del nmero de minas que hay alrededor de la celda (f,c). Podramos redactar un cdigo ms largo para realizar este recorriod, pero podemos simplificar un poco las cosas si consideramos en las coordenadas de la celda 1 son (f-1, c-1), las de la celda 2 son (f-1, c+0), las de la celda 3 son (f+1, c+1), y as sucesivamente. Esto es, podemos crear dos arreglos, dx y dy que contengan los incrementos que hay que hacer al valor de f y c para obtener las coordenadas de las celdas en la vecindad de la celda (f, c). int[ ] dx = new int[8] {-1, 0, 1, 1, 1, 0, -1, -1}; 1, 0};

int[ ] dy = new int[8] {-1, -1, -1, 0, 1, 1,

Por tanto, ahora basta redactar un ciclo con un contador de 0 a 8 para visitar las 8 celdas alrededor de la celda (f,c) y obtener las coordenadas de cada una de sus vecinas. Claro, cuando el valor la nueva coordenada resultante sea menor que 0 o mayor que 9, significar que estamos en una celda del borde, y en este caso debemos ignorar estas coordenadas, pues nos llevan fuera del campo minado. As que si llamamos vx y vy a las coordenadas de las celdas en la vecindad de la celda (f, c), nuestro cdigo para recorrer la vecindad de una celda dada sera algo as. for ( int v = 0; v < 9; v++) { vx = f + dx[v]; vy = c + dy[v]; } Pero necesitamos una manera de saber a qu celda (TabIndex) estamos apuntando de manera lineal, porque, a fin de cuentas, nuestras celdas (botones) tienen asignado un valor lineal de 0 a 99, no dos rectangulares, f y c. Dicho de otra manera, necesitamos una funcin inversa que nos permita, dadas la fila y la columna de una celda, obtener su posicin lineal en el campo minado. Para dicha nuestra, esta funcin es bien sencilla. p = 10 * f + c; Por ejemplo, la celda (3,4) tiene posicin lineal 10*3 + 4, que es 34, que lo convertimos de vuelta a filas y columnas, comprobamos que nos f = 3, c = 4. Y si consideramos el caso de la celda (0,9), p = 10*0 + 9, que nos da de vuelta (0, 9). El lector debe asegurarse de esta funcin simepre nos da el valor correcto. Si tomamos en cuenta lo apuntado acerca de no salirnos del campo minado, entonces vemos que una vez obtenidas las coordenadas bidimensionales de una celda mediante los valores de vx y vy, basta obtener el valor de p, segn la funcin recin estrenada, y caer en cuenta que su valor debe estar entre 0 y 100, porque de otra manera caemos fuera del campo minado. Por tanto, para recorrer todo el campo minado nos basta un ciclo con un contador de 0 a 99, y para cada uno de estos valores recorremos sus ocho posibles vecinas, ignorando los casos en que caemos fuera del campo. int mv, f, c; for (int t = 0; t < 100; t++) { if (mt[t] != 9)

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

mv = 0; f = t / 10; c = t % 10; for (int v = 0; v < 9; v++) { vx = f + dx[v]; vy = c + dy[v]; p = 10* vx + vy; if (p > -1 && p < 100 && mt[p] == 9) mv++; } mt = mv;

} Este cdigo empieza por declarar la variable entera mv para llevar la cuenta de la cantidad de minas en la vecindad de una celda, y las variables f y c para obtener la fila y la columna de cada celda visitada en la matriz mt. A seguidas hay un ciclo for para recorrer todas las celdas en la matriz mt, desde la 0 hasta la 99. Y dentro de este ciclo for hay otro, de manera que se aplica a cada una de las celdas en la matriz mt. En este ciclo interior lo primero que se pregunta, con un if, si esta celda de la matriz NO hay un 9, porque de ser as significa que hay una mina y no tiene sentido contar cuntas minas hay a su alrededor, porque tal cosa slo tiene sentido en caso de que NO haya una mina en la celda investigada. As que en caso de que la celda no contenga un 9 entonces se have cero a la cantidad de minas encontradas en la vecindad de la celda actual, mv, y se calcula la fila y la columna de la celda visitada en la matriz mt. Entonces se recorre con un ciclo for las 8 celdas en la vecindad de la celda actual usando la variable contador v. Calculamos las coordenadas de la celda en la vecindad de la celda (f, c) usando las variables vx y vy, y este valor lo convertimos en su equivalente lineal, p. Si el valor de p obtenido es mayor que -1 y menor que 100, entonces se trata de una celda dentro del espacio minado, por tanto, si adems para esta misma posicin en la matriz mt hay un 9, entonces hemos encontrado una mina en la vecindad de la celda (f, c) y la contamos incrementando el valor de la variable mv, que lleva la cuenta de las minas encontradas en esta vecindad. Una vez salimos del ciclo fo que recorre la vecindad de la celda (f, c), asignamos a la celda en la matriz mt el valor mv, para que refleje el nmero de minas encontradas en su vecindad. Este cdigo es un poco largo, por lo que conviene encapsularlo en un mtodo, digamos, vecindad, el cual no retorna valor porque lo que hace es colocar en la matriz mt los valores correspondientes a los nmeros de minas en las vecindades de las celdas y la matriz misma, mt, est declarada como de mbito global. Tampoco necesitamos pasar ningn parmetro al mtodo vecindad por las mismas razones. Por tanto, nuestro mtodo vecindad, que insertamos inmediatamente debajo del mtodo minas, queda as:
private void vecindad() { int mv, f, c; for (int t = 0; t < 100; t++) { if (mt[t] != 9) {

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

mv = 0; f = t / 10; c = t % 10; for (int v = 0; v < 9; v++) { vx = f + dx[v]; vy = c + dy[v]; p = 10* vx + vy; if (p > -1 && p < 100 && mt[p] == 9) mv++; } mt = mv; } } }

Al insertar el mtodo vecindad Visual C# nos informa que las variables dx y dy no han sido declaradas, lo cual es cierto. Surge ahora la inquietud de dnde declarar estas variables, y la respuesta obvia es que como su mbito debe ser global, porque deben estar disponibles para cada celda de la matriz mt al recorrerla, se deben declarar inmediatamente debajo de donde declaramos el arreglo para almacenar temporalmente las posiciones aleatorias de las 25 minas.
int[] dx = new int[8] { -1, 0, 1, 1, 1, 0, -1, -1 }; // delta x para calcular vecindad int[] dy = new int[8] { -1, -1, -1, 0, 1, 1, 1, 0 }; // delta y

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

Pero tambin Visual C# nos informa que nos falta declarar las variables vx, vy y p, lo cual debe hacerse dentro del propio mtodo vecindad, es decir, inmediatamente debajo de la declaracin de mv, f y c. int mv, f, c; // minas en vecindad, fila, columna int vx, vy, pv; // x de vecindad, y de vecindad, posicin vecindad

Note que hemos insertado los comentarios para las 6 variables declaradas en la cabecera del mtodo vecindad, y, note que hemos cambiado, por considerarlo ms apropiado, el nombre de la variable p por pv. Con todo, Visual C# nos sigue informando que queda un error y es que estamos tratando de asigna a la matriz completa mt un valor escalar y esto no se puede. mt = mv; Lo que en realidad queremos es asignarle el valor mv, cantidad de minas encontradas en la vecindad de la celda de posicin lineal t, es decir, mt[t]. mt[t] = mv;

Estamos, pues, listos para verificar si todas estas tonteras que hemos estado diciendo realmente funcionan. Para ello nos vamos al cdigo del evento Click (botn 1) e insertamos una invocacin al mtodo vecindad, inmediatamente despus de invocar al mtodo minas. Recordemos que hasta ahora al

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

pulsar el botn se desplegaban puros 9 y ceros, pero esperamos que ahora se desplieguen los 9 y los dgitos del 0 al 8 correspondientes alrededor de cada 9. vecindad();

Ejecutamos la aplicacin, pulsamos el botn y vemos que se queda pensando un rato, y luego, para nuestra vergenza, produce un error en tiempo ejecucin.

Nos dice que nos aseguremos que no estamos excediendo los lmites del mximo ndice de una lista, y al ver la lnea que parece en amarillo vemos que el arreglo sospechoso es dx, por qu?, porque la variable v toma todos los valores de 0 a 9, pero la posicin 9 no existe en el arreglo dx, de hecho, tampoco en dy. Por tanto, para corregir este error basta recordar que los arreglos dx y dy fueron declarados de tamao 8, esto es, slo existen en ellos las posiciones de 0 a 7, y estamos pretendiendo acceder las posiciones de la 0 a la 8, de ah el error, pues la posicin 8 no existe! Este error se corrige, sencillamente, cambiando el 9 del for por 8. Detenemos la aplicacin y cambiamos el valor.

Ejecutamos nuevamente la aplicacin y pulsamos el primero botn, esta vez con los dedos cruzados, a ver qu pasa. Aqu entre nosotros, esto es magia blanca Cada vez que pulsamos el botn las minas

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

cambian de posicin, pues se generan nuevamente y nuevamente se calculan se calculan y despliegan los valores de la vecindad de cada mina. Parece que no hablbamos tonteras, despus de todo! Es increble lo que un poco de cdigo C# puede hacer, y qu divertido! Pero hemos estado probando nuestro cdigo, a medida que lo hemos ido creando, invocando nuestros mtodos desde el evento Click del primer botn, pero es claro que sta no es la manera como funciona realmente el juego. Sin embargo, como somos personas normales y no sper genios que pueden planificar todo el cdigo de antemano, perdindose buena parte de la diversin, hemos ido, por decirlo as, usado al evento Click del botn 1 como un andamio para apoyarnos al construir este juego, que es un poco complicadito, por lo que hemos visto. Sera adecuado que los eventos minas y vecindad, que genera las minas al azar y calcula los valores de las vecindades, respectivamente, se ejecutaran al momento en que el Form se despliegue, y esto logramos colocando estas invocaciones en el evento por omisin del Form, a saber, Form_Load, el cual se genera rpidamente al hacer doble clic sobre el Form, siempre que no haya otro componente (como un botn, por ejemplo) debajo. As que pulsamos doble sobre el Form y se genera el evento Form_Load (si ya el lector descuidado no lo ha hecho). Note que se genera al final del cdigo en la clase, por lo que quedan las dos llaves cerradas luego de l. Tambin el cursor de texto est listo para recibir nuestro cdigo.

Cortamos las invocaciones de los eventos minas y vecindad del evento Click del botn 1 y las pegamos en el interior del evento Form_Load.

Al ejecutar la aplicacin comprobamos que ahora los valores aparecen la primera vez que se pulsa el botn (aunque los eventos minas y vecindad ha sido ejecutados, pues este evento se ejecuta antes que la forma, incluso, se dibuje), pero no cambian al pulsar otra vez el botn. Esto es as porque ahora los eventos minas y vecindad se ejecutan una sola vez y al pulsar el botn no se muestran cambios porque no los ha habido. Ahora podremos redactar el cdigo para comportamiento esperado de los botones.

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

Pero antes el lector debe notar que el nmero de minas mostrado en las celdas no siempre es correcto, pues a veces en el borde vertical de la izquierda cuenta las minas que estn en el borde de la derecha, y, en general, las minas que estn en un borde se cuentan en el otro, y esto se debe a que en realidad es un error asumir que porque el valor de pv est entre 0 y 100, y es vlido, pues hemos cado dentro de la matriz NO es correcto aceptar que una celda del borde izquierdo, por ejemplo, est en la vecindad de otra, en la misma fila, pero en el borde derecho. Por tanto, tendremos apelar, estrictamente, a los valores de f y c, esto es, de la fila y la columna, como criterio para establecer la vecindad a una celda. Naturalmente, este error slo aplica a las celdas en los bordes, y cuando, especficamente, caen minas en los bordes. El cdigo corregido es ste. if (vx > -1 && vx < 10 && vy > -1 && vy < 10 && mt[pv] == 9) mv++; Esta lnea de cdigo sustituye a la del if que hay en el mtodo vecindad.

Ahora s podemos proceder a redactar el cdigo del botn 1 para que responda adecuadamente. Empecemos por hacer que muestre el estado o valor numrico que hay en la matriz de estado subyacente en esa misma posicin. Sustituimos el cdigo actual en el evento Click del botn 1 por: int t = ActiveControl.TabIndex; ActiveControl.Text = Convert.ToString(mt[t]); Note que se crea la variable entera t y se le asigna el valor del TabIndex del control. Luego se convierte a string el valor de la matriz de estado que est en la posicin t, es decir, el que corresponde a la cantidad de minas que hay en la vecindad del botn correspondiente. Hacemos que los 100 botones compartan el evento Click del botn 1 y podremos comprobar que al pulsar cualquier botn nos muestra el nmero de minas que hay alrededor de cada botn pulsado. Ya estamos casi terminando. Ahora tenemos que hacer que cuando se pulse un botn donde hay una mina se muestren todos todos los botones donde hay mina, y, en lugar de un 9 vamos a mostrar un asterisco en lugar de un nueve. Para mostrar que el juego se acab porque se ha pisado una mina, y mostrar todas las casillas donde hay una mina, con un asterisco, recorreremos los 100 botones y verificaremos si en la matriz de estado hay un nueve en la celda correspondiente, y en ese caso mostramos un asterisco en ese botn.

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

int t = ActiveControl.TabIndex; if (mt[t] != 9) ActiveControl.Text = Convert.ToString(mt[t]); else foreach (System.Windows.Forms.Control ctrl in this.Controls) { int ta = ctrl.TabIndex; if (mt[ta] == 9) ctrl.Text = "*"; }

Vayamos al modo de diseo y aumentemos el Font, digamos de los botones, digamos y pongmoslo en negritas. Para que los asteriscos salgan en rojo el cdigo completo del evento Click del botn 1 es: private void button1_Click(object sender, EventArgs e) { int t = ActiveControl.TabIndex; if (mt[t] != 9) ActiveControl.Text = Convert.ToString(mt[t]); else foreach (System.Windows.Forms.Control ctrl in this.Controls) { int ta = ctrl.TabIndex; if (mt[ta] == 9) { ctrl.ForeColor = Color.Red; ctrl.Text = "*"; } }

Note que cuando hay un nueve en la matriz de estado el color de la propiedad ForeColor se cambia a rojo, para que los asteriscos salgan rojos. Slo nos falta que al pulsar el botn derecho sobre un botn aparezca un carcter especial en el botn, para indicar que el usuario CREE que en esa posicin hay una mina. Pero aqu nos encontramos con un problemita que nos tom horas de investigacin en la web, pero finalmente, gracias a Dios, hallamos un respueta simple. El problemita es que Visual C#, y de hecho, .NET, no tiene un evento right-click para los botones, sino que asume que cuando se pulsa un botn se ha pulsado el botn izquierdo, left-click. Si hacemos, como un nuestro caso, los botones respondan al evento Click, no podremos discriminar cundo el usurio ha pulsado el botn izquierdo o el derecho, cuando ha hecho left o right click. Para discriminar entre ambos botones invocamos el evento MouseDown, que se dispara cada vez que se pulse el botn, y una vez all preguntamos cul de los dos botones fue pulsado, simple! Un ejemplo podra ser:

private void button1_MouseDown(object sender, MouseEventArgs e) {

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

if (e.Button == MouseButtons.Right) MessageBox.Show("Right click."); if (e.Button == MouseButtons.Left) MessageBox.Show("Left click.");

Note que el argumento e que es pasado al evento contiene una propiedad Button, y cuando su valor es MouseButtons.Right significa que se ha pulsado el botn derecho, y cuando es Mouse.Buttons.Left significa que se ha pulsado el botn izquierdo. Ya nosotros hemos asociado el cdigo que hemos probado al evento Click del botn, pero eso no es problema, porque si dejamos el evento sin cdigo, el evento se disparara, pero no tendra acciones concretas qu ejecutar, por lo que resulta inofensivo. Lo que haremos, pues, es agregar el evento MouseDown al primer botn y mover all el cdigo que tenamos en el evento Click (dejndolo ahora vaco, por supuesto). Luego haremos provicin para discriminar entre un left-click y un right-click. Empecemos, pues, por asociar el evento MouseDown al primer botn. Lo primero es ir al modo de diseo y seleccionar el primer botn.

Asegurando que el pointer del mouse est sobre el botn seleccionado, pulsamos el botn derecho del mouse (right-click) para que se despliegue el men de contexto y pulsamos la opcin Properties.

En el centana de propiedades pulsamos el botn de eventos, all buscamos el evento MouseDown y lo pulsamos doble (double click).

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

Se muestra entonces el modo de cdigo y el cursor en el interior del recin auto aadido evento MouseDown.

Seleccionamos todo el cdigo en el interior del evento button1_Click (entre la dos llaves, sin incluirlas) y lo cortamos (Ctrl+X).

Entonces lo pegamos (Ctrl+V) en el interior del mtodo MouseDown.

Ejecutamos la aplicacin para comprobar que ahora slo el primer botn responde al evento MouseDown (Click, mientras tanto), por lo cual debemos compartir este evento con todos los botones, como hicimos con el evento Click en su momento. Para ello seleccionamos todos los botones y en la ventana de propiedades, como la pestaa de eventos est todava expuesta, seleccionamos el evento Botton1_MouseDowm.

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

Ejecutamos la aplicacin y comprobamos que todos los botones responden al pulsarlos, tanto con el botn izquierdo, pero no al derecho, exactamente como antes del traslado de cdigo. Lo cual significa que nos falta discriminar, por cdigo, entre clic izquierdo y clic derecho. Para lograr esto nos aseguramos que el cdigo en el interior del evento MouseDown sea:
if (e.Button == MouseButtons.Left) { int t = ActiveControl.TabIndex; if (mt[t] != 9) ActiveControl.Text = Convert.ToString(mt[t]); else foreach (System.Windows.Forms.Control ctrl in this.Controls) { int ta = ctrl.TabIndex; if (mt[ta] == 9) { ctrl.ForeColor = Color.Red; ctrl.Text = "*"; } } } if (e.Button == MouseButtons.Right) MessageBox.Show("Right click."); Note que es exactamente el mismo cdigo de antes, excepto que se le han aadido las lneas sombreadas. Ejecutamos la aplicacin y comprobamos que al pulsar el botn izquierdo del mouse sobre un botn cualquiera, responde como antes, pero al pulsar el botn derecho se despliega una caja de dilogo, que no se cierra y no nos permite seguir jugando, hasta pulsamos el botn OK que muestra en su interior. Por cierto, aprovechemos y modifiquemos la propiedad Position del Form para que la caja de dilogo salga en el centro del For,, y no nos obligue a irla a buscar! Para ello tenemos que pulsar, primero, el botn Properties den la ventana de propiedades para que nos muestre las propiedades y no los eventos.

Ahora nuestro programa est respondiendo ambos botones del mouse. Nos falta ahora que al pulsar el derecho no nos ejecute el cdigo de prueba que muestra la caja de dilogo, sino el que realmente deseamos, a saber, que se muestre un carcter especial, digamos un , para indicar que el usuario cree que en esa celda hay un a mina.

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

Para ello, basta que remplacemos la invocacin al MessageBox cuando se pulsa el botn derecho por este cdigo:

ActiveControl.Text = ""; Es decir, el cdigo debe lucir as.

Al ejecutar la aplicacin nos encontramos con una desagradable sorpresa, al pulsar el botn derecho sobre cualquier botn el carcter especial se muestra no sobre el botn que pulsamos, sino sobre el botn que est activo! Cmo resolver esto? Luce complicado porque cuando se pulsa el botn izquierdo el botn pulsado se convierte automticamente en el control activo, pero no ocurre as con al pulsar el botn derecho. Pero luego de un par de horas de bsqueda en la web, encontramos esta sencilla solucin.

if (e.Button == MouseButtons.Right) { Button btn = (Button)sender; btn.Text = ""; } Lo que hace este cdigo es instanciar a un botn, btn, el parmetro sender que enva Windows al evento MouseDown, y asignarle el carcter a su propiedad Text. As que el cdigo completo del evento MouseDown es:
private void button1_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { int t = ActiveControl.TabIndex; if (mt[t] != 9) ActiveControl.Text = Convert.ToString(mt[t]); else foreach (System.Windows.Forms.Control ctrl in this.Controls) { int ta = ctrl.TabIndex; if (mt[ta] == 9) { ctrl.ForeColor = Color.Red; ctrl.Text = "*"; } } }

if (e.Button == MouseButtons.Right) { Button btn = (Button)sender; btn.Text = ""; } }

Ya podemos jugar al busca minas. Se deja como tarea al lector que al pulsar una mina se despliegue una caja de dilogo diciendo Lo siento. al usuario y cuando se cierre esta caja de dilogo se reinicie el juego, esto es, limpiando los Text de los botones y generando nuevamente las minas.

intec. elementos (ing-202). ecabrera. mayo 2010. minesWeeper, ejemplo 5

Tambin se deja de tarea hacer que el campo minado sea de 20x20, para soportar tres niveles de juego: Principiante, Intermedio y Avanzado, con campos minados de 5x5, 10x10 y 20x20, en que se generan 5, 25 y 50 minas, respectivamente. Para reducir el campo minado a la vista del usuario se asigna el valor false a la propiedad Visible de los botones que quedan fuera del juego y se reduce el el Size del Form consecuentemente.

ecabrera, mayo 20010.

También podría gustarte