jueves, 19 de abril de 2012

Tic Tac Toe - Silverlight(Programmatically)

Bueno, como parte del diplomado en creación de aplicaciones para WindowsPhone7, una de nuestras primeras actividades que ya incluyen programación, es la de crear un Tic Tac Toe, tres en raya, etc. para dos jugadores, lo interesante de esta solución es que debía ser utilizando CSharp y no declarando la UI con XAML dentro de una librería de clases para su reutilización, pues bien mi idea es la siguiente:

  • Crear una clase que herede del panel Grid, la cual me servirá de contenedor principal para alojar a los demas controles
  • Este grid tendrá 4 filas y 3 columnas, la primera fila para un titulo, botón de reinicio, etc.
  • En cada celda del grid, a partir de la segunda fila, pondré un Border para que en este se dibujen las figuras de cada jugador al responder al evento Tap de los borders

Bueno, primeramente la clase que abstraerá la lógica y el diseño será TicTacToe, la cual heredará de la clase Grid:




public class TicTacToe : Grid {
   ...
}

Ahora el siguiente paso es crear las celdas dentro de nuestro grid, al ser ya esta clase un Grid, no es necesario instanciar un panel Grid, entonces simplemente basta acceder a sus miembros y métodos a travéz de base o this


private void InitializeComponent() {
    RowDefinitions.Add(new RowDefinition() {
       Height = new GridLength(200, GridUnitType.Pixel)
   });
   RowDefinitions.Add(new RowDefinition());
   RowDefinitions.Add(new RowDefinition() {
       Height = new GridLength(1, GridUnitType.Star)
   });
   RowDefinitions.Add(new RowDefinition());

   // Define columnas
   ColumnDefinitions.Add(new ColumnDefinition());
   ColumnDefinitions.Add(new ColumnDefinition() {
       Width = new GridLength(1, GridUnitType.Star)
   });
   ColumnDefinitions.Add(new ColumnDefinition());
}

Con el anterior snippet creamos en el grid actual nuestras 4 filas y 3 columnas, el siguiente pasó que utilicé en mi lógica fue el de utilizar un border en cada casilla a partir de la segunda fila:


Border00 = new Border();
Border00.Name = "border_0_0";
Border00.BorderThickness = new Thickness(0, 0, 6, 6);
Border00.BorderBrush = new SolidColorBrush(Color.FromArgb(0xff, 0x44, 0x44, 0x44));
Border00.Background = new SolidColorBrush(Color.FromArgb(0xff, 0xdd, 0xdd, 0xdd));
Border00.SetValue(Grid.RowProperty, 1);
Border00.SetValue(Grid.ColumnProperty, 0);
...
Border00.Tap += new EventHandler(Border_Tap);

Cada border tendrá el nombre border_[fila]_[columna], esto con propósitos de recuperar la celda de la cual se hizo Tap, bueno luego las siguiente propiedades son para el color, la forma de los bordes, en que fila y columna del grid se encuentran y finalmente le asocio un handler para el evento Tap a todos los bordes, es decir, cuando se haga Tap en cualquir border de los 9, llamarán al método Border_Tap.

Teniendo en claro la forma en como se trabajará, pasemos un momento a la parte lógica, primero debemos establecer un tablero lógico para ambos jugadores, podemos establecer este tablero en una arreglo bidimensional de tipo bool, true si hay pieza o false si no; o utilizar un int y utilizarlo como dos tableros y usar operaciones a nivel de bits para ver si cierta posición está utilizada. Ambas son válidas pero por cuestiones de simplicidad de presentación opte por la de usar un arreglo.

Entonces, en cada Tap debemos establecer en que posición se hizo tap, y ponerla en el tablero lógico del jugador que lo realizó, una vez hecho esto siempre es necesario verificar si en el tablero lógico del jugador existe una combinación ganadora, para eso en una clase TicTacToeUtil cree un método estático que recibe como entrada un arreglo bidimensional, y verifica si existe combinación ganadora:


public static bool HasWinner(bool[,] m) {
    for(int i = 0; i < 3; i++) {
        if (m[i, 0] && m[i, 1] && m[i, 2]) {
            return true;
        }
        else {
            if (m[0, i] && m[1, i] && m[2, i]) {
                return true;
            }
        }
    }
    if ((m[0, 0] && m[1, 1] && m[2, 2]) || (m[0, 2] && m[1, 1] && m[2, 0]))
        return true;

    return false;
}

Pues bien, lo que hace es iterar tres veces para ver si exite fila o columna con tres elementos en la misma, en caso de que no haya una solución fila o columna, verifica la diagonales y finalmente retorna false indicando que no existe solución para este arreglo dado.

Bueno, una vez se tenga la lógica de solución, pasemos a definir la lógica del tablero, primero defninimos como miembros de la clase TicTacToe dos arreglos de tipo bool, para ambos jugadores que serán sus tableros lógicos y dos flags, una para ver si ya terminó el juego y otra para ver a quien le toca jugar:


public partial class TicTacToe : Grid {
   private bool[,] jugador1;
   private bool[,] jugador2;
   private bool juegaJugador1;
   private bool gameOver;   
   ...
}


Finalmente, veamos el método Border_Tap que se encargará de dibujar figuras en las casillas, ver si algún jugador gano, etc.


private void Border_Tap(object sender, GestureEventArgs e) {
   if (sender is Border) {
      e.Handled = true;
      if (!gameOver) {
         Border cell = sender as Border;

         int row, col;
         GetPosition(cell.Name, out row, out col);

         if (!jugador1[row, col] && !jugador2[row, col]) {
            if (juegaJugador1) {
               RegularPolygon x = new RegularPolygon();
               x.PointCount = 5;
               x.InnerRadius = 0.47211;
               x.Fill = new SolidColorBrush(Color.FromArgb(0xFF, 0x2F, 0x2F, 0x2F));
               x.Margin = new Thickness(21.333, 27.333, 21.333, 44.333);
               x.Stroke = new SolidColorBrush(Colors.Black);
               x.UseLayoutRounding = false;

               cell.Child = x;
               jugador1[row, col] = true;

               if (TicTacToeUtil.HasWinner(jugador1)) {
                  gameOver = true;
                  MessageBox.Show("Gano jugador 1");
               }
            }
            else {
               Arc y = new Arc();
               y.ArcThickness = 20;
               y.ArcThicknessUnit = Microsoft.Expression.Media.UnitType.Pixel;
               y.EndAngle = 360;
               y.Fill = new SolidColorBrush(Colors.White);
               y.Margin = new Thickness(17.333, 24.667, 15, 29.667);
               y.Stretch = Stretch.None;
               y.Stroke = new SolidColorBrush(Colors.Black);
               y.StartAngle = 0;
               y.UseLayoutRounding = false;

               cell.Child = y;
               jugador2[row, col] = true;

               if (TicTacToeUtil.HasWinner(jugador2)) {
                  gameOver = true;
                  MessageBox.Show("Gano jugador 2");
               }
            }

            juegaJugador1 = !juegaJugador1;
         }
      }
   }
}

Bueno, en palabras simples verifica que el juego todavía no haya terminado, luego del border que causo el evento obtiene su nombre del cual obtendrá las posiciones (recuerden que el formato es xxxx_fila_columna), luego si ninguna de estas posiciones esta utilizada verifica de quien es el turno y en su tablero virtual establece un valor a true, luego dibuja la ficha y verifica si ganó, caso contrario le da el turno al otro jugador y se repite este proceso mientras un jugador gane, en este caso se pone el flag gameOver a true, para que ningún jugador pueda seguir pintando las celdas.

Bueno, los demás controles y métodos son un poco irrelevantes, pero son de utilidad. En fin ... aca les dejo el link de descarga
Espero les sea de utilidad, sin tener mas con que aburrirlos XD, me despido que ya estoy tarde para mis clases.

Saludos

1 comentario:

  1. Holass!

    Una idea para obtener la fila y columna...
    Ya que el Border está en un Grid, puedes utilizar los métodos estáticos Grid.GetRow y Grid.GetColumn para obtener la fila y columna (respectivamente).

    Saludos!

    ResponderEliminar