Sudoku

Sudoku
El post de hoy va sobre la generación de un sudoku.
Para el que no lo sepa, un sudoku consta de una matriz de 9 cuadrados de 3×3, los cuales contienen 9 números del 1 al 9 cada uno.
La característica principal del sudoku, es que no se repite ningún número en las filas, columnas y cuadrados de 3×3. Un ejemplo de ello, sería el siguiente:sudoku01Podéis comprobar que en todas las filas, se encuentran los números del 1 al 9
los cuales no se repiten; si analizamos las columnas, ocurre lo mismo y si lo hacemos con los cuadrados de 3×3, pasa igual.Para generar una matriz completa de sudoku, debemos crear un método que inicie una secuencia y en el caso de que no sea idónea, es decir que se repita algún número o que no se pueda incluir, debe iniciarla de nuevo hasta encontrar una que lo sea, pero para ello, debemos ayudarle a escoger el buen camino y que el número de repeticiones sea el menor posible.Llevándolo a la práctica, el método que he utilizado para generar la matriz es el siguiente:Lo primero de todo, es optimizar el rendimiento de nuestra aplicación y para hacerlo, nos ayudaremos del objeto List, el cual permite añadir (Add), eliminar(remove) y obtener un objeto mediante su índice. En él, añado los números del 1 al 9 y obteniendo un número aleatorio desde 0 al número de objetos de la lista, puedo ir extrayendo como si de un bombo se tratara cada número, así nos aseguramos que los números no se repiten.En primer lugar, esta primera secuencia la creamos por filas.

En cada una de las posiciones, elimino los elementos que están a la izquierda o en su misma fila, elimino los que están por encima o los que pertenecen a la misma columna y por último, elimino los que se encuentran en el mismo cuadrado de 3×3.

De estos números candidatos, debemos elegir el más idóneo, pero… dejando que el azar haga su trabajo y para realizar esto, calculo la probabilidad de incluir el número en la fila o lo que es lo mismo, determinar el número de veces que puede incluirse en las siguientes posiciones. Recordemos que la probabilidad de un suceso es igual al número de casos favorables o el número de casos que cumplen con nuestro objetivo, dividido entre el número de casos totales o posibles. En nuestro caso, si estamos decidiendo obtener el número central del cuadrado, el número con coordenadas 4,4 (en la imagen equivale al número 7 central, las coordenadas van desde 0 hasta 8), la probabilidad de incluir el número 7 en las siguientes columnas sería de 3/5, es decir 3 columnas en las que es posible incluir el número 7 de las 5 que quedan.

sudoku02

Pongamos un ejemplo

sudoku03

La posición con coordenadas 4,5, tiene como números candidatos el 1 y el 6; el 1 tiene un 50 % de probabilidad de colocarse en las siguientes columnas, (puede incluirse en la columna 5 y 8 sobre 4 columnas)  y el 6 un 75% (puede incluirse en la columna 5,6 y 8 sobre 4 columnas), por tanto lo que hacemos es “incentivar” a los que tienen menos probabilidad con un número igual a 1-probabilidad o probabilidad de no ocurrencia, en este caso creo un conjunto con un número igual a la probabilidad de no ocurrencia multiplicado por 10; en el ejemplo creo un conjunto de 0.5 x 10=5 números 1 y 0.25 x 10=2,5 o 2 números 6 {1,1,1,1,1,6,6}, seleccionamos un número al azar y ya tenemos nuestra elección, el número 1 con más probabilidad de incluirse que el 6, pero ambos están en el bombo y así no descartamos futuras combinaciones válidas.

La secuencia va aumentando hasta que, o no tiene ningún candidato en alguna de las posiciones y por tanto debe comenzar de nuevo o llega al final de la secuencia con una combinación de números adecuada gracias a nuestra ayuda probabilística.

Esto aplicado a código, lo hago con C# y WPF, me encanta escribir y no escribir código! me encanta escribir clases independientes que no tengan ninguna relación con la interfaz gráfica y me encanta crear formularios sin código (C# o VB .NET).

Como clase ViewModel, la clase SudokuManager, las dos implementan la interfaz INotifiPropertyChanged para que podamos enlazar los datos de nuestro código XAML y de las cuales podemos mostrarlas mediante el siguiente diagrama de clases

sudoku04.png

Creo una clase llamada Point para cada posición de la matriz con las coordenadas, el valor de la casilla, una propiedad de solo lectura para calcular el cudrado de 3×3 al que pertenece y si la posición es visible para ocultarla en el futuro y poder calcularla.

En la clase SudokuManager, tenemos una propiedad que es una colección de estas 81 posiciones, dos propiedades una para obtener el tiempo de generación en milisegundos y otra para ver el número de iteraciones que ha necesitado para generar el sudoku completo y una vez que este ha sido creado correctamente, la propiedad IsGood pasa a True.

Como métodos públicos, Start para generar una matriz nueva y ToString para obtener la lista completa textualmente. Tengo otros métodos para ocultar los números en base a la dificultad y poder jugar o poder calcular el Sudoku automáticamente, pero eso quedará para otro día.

Como maquinaria interna o métodos privados, el principal es el método setSelection al cual le pasamos como parámetro el objeto Point o cuadrícula de la matriz, de la lista inicial del 1 al 9, le quita los elementos de su izquierda o de su misma fila, los elementos superiores o de su columna y los elementos de su cuadrícula de 3×3, una vez hecho esto, solo quedarían los números candidatos y llamaría al método getSelection, el cual calcula la probabilidad que hemos explicado y obtiene el número definitivo que se le asignará a la posición.

Una vez hecho esto, vamos a ver si el rendimiento es adecuado, ya que imaginad que para generar una matriz, necesitara 5 segundos, algo inaceptable para los tiempos que corren, por lo que he realizado varias muestras de 50 generaciones y de todas ellas, obtengo un promedio de 53,58 iteraciones y un promedio de 192 milisegundos por cada una, estando el valor mínimo de 1 iteración y 5 ms (con un I5 2400, que no es un I7 6500!)  y unos valores máximos de 165 iteraciones y medio segundo de los cuales hay que decir que tienen una probabilidad de salir de un 2%, o una probabilidad de un 16% de superar las 100 iteraciones y los 350ms.

Bueno, para ser el primero, creo que no está mal, seguro que se pueden arañar algún milisegundo en alguna operación.

En cuanto al XAML, he creado un control de usuario para que no sea muy extenso el código de la página principal, al cual le pasamos como DataContext la clase SudokuManager y a cada posición, le asignamos como DataContext el objeto Point que le pertenece, de modo que desde el estilo, podemos hacer un Binding a sus propiedades, un poco de código como muestra

<UserControl x:Class="Ramanujan.Matriz"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Ramanujan"
             mc:Ignorable="d" 
             d:DesignHeight="490" d:DesignWidth="490">
    <Grid x:Name="grid">        
        <Grid.Resources>
            <!-- CONVERTER -->
            <local:BoolToVisibleConverter x:Key="b2vc"/>
            <!-- ESTILO DE TEXTBLOCK -->
            <Style x:Key="textBlockStyle" TargetType="TextBlock">
                <Setter Property="HorizontalAlignment" Value="Center"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="TextAlignment" Value="Center"/>
                <Setter Property="FontSize" Value="24"/>
                <Setter Property="Visibility" Value="{Binding IsVisible,Converter={StaticResource b2vc}}"/>
                <Setter Property="Text" Value="{Binding Value}"/>
            </Style>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="20*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="20*"/>
        </Grid.RowDefinitions>
        <Border Grid.Column="1" Grid.Row="1" Grid.RowSpan="9" Grid.ColumnSpan="9" BorderBrush="WhiteSmoke" BorderThickness="2"/>
        <Border Grid.Column="1" Grid.Row="1" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="4" Grid.Row="1" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="7" Grid.Row="1" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="1" Grid.Row="4" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="4" Grid.Row="4" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="7" Grid.Row="4" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="1" Grid.Row="7" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="4" Grid.Row="7" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <Border Grid.Column="7" Grid.Row="7" Grid.RowSpan="3" Grid.ColumnSpan="3" BorderBrush="WhiteSmoke" BorderThickness="1"/>
        <TextBlock x:Name="textBlock00" DataContext="{Binding Points[0]}" Grid.Column="1" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock01" DataContext="{Binding Points[1]}" Grid.Column="2" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock02" DataContext="{Binding Points[2]}" Grid.Column="3" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock03" DataContext="{Binding Points[3]}" Grid.Column="4" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock04" DataContext="{Binding Points[4]}" Grid.Column="5" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock05" DataContext="{Binding Points[5]}" Grid.Column="6" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock06" DataContext="{Binding Points[6]}" Grid.Column="7" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock07" DataContext="{Binding Points[7]}" Grid.Column="8" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock08" DataContext="{Binding Points[8]}" Grid.Column="9" Grid.Row="1" Style="{StaticResource textBlockStyle}"/>

        <TextBlock x:Name="textBlock10" DataContext="{Binding Points[9]}" Grid.Column="1" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock11" DataContext="{Binding Points[10]}" Grid.Column="2" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock12" DataContext="{Binding Points[11]}" Grid.Column="3" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock13" DataContext="{Binding Points[12]}" Grid.Column="4" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock14" DataContext="{Binding Points[13]}" Grid.Column="5" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock15" DataContext="{Binding Points[14]}" Grid.Column="6" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock16" DataContext="{Binding Points[15]}" Grid.Column="7" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock17" DataContext="{Binding Points[16]}" Grid.Column="8" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock18" DataContext="{Binding Points[17]}" Grid.Column="9" Grid.Row="2" Style="{StaticResource textBlockStyle}"/>

        <TextBlock x:Name="textBlock20" DataContext="{Binding Points[18]}" Grid.Column="1" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock21" DataContext="{Binding Points[19]}" Grid.Column="2" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock22" DataContext="{Binding Points[20]}" Grid.Column="3" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock23" DataContext="{Binding Points[21]}" Grid.Column="4" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock24" DataContext="{Binding Points[22]}" Grid.Column="5" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock25" DataContext="{Binding Points[23]}" Grid.Column="6" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock26" DataContext="{Binding Points[24]}" Grid.Column="7" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock27" DataContext="{Binding Points[25]}" Grid.Column="8" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock28" DataContext="{Binding Points[26]}" Grid.Column="9" Grid.Row="3" Style="{StaticResource textBlockStyle}"/>

        <TextBlock x:Name="textBlock30" DataContext="{Binding Points[27]}" Grid.Column="1" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock31" DataContext="{Binding Points[28]}" Grid.Column="2" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock32" DataContext="{Binding Points[29]}" Grid.Column="3" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock33" DataContext="{Binding Points[30]}" Grid.Column="4" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock34" DataContext="{Binding Points[31]}" Grid.Column="5" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock35" DataContext="{Binding Points[32]}" Grid.Column="6" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock36" DataContext="{Binding Points[33]}" Grid.Column="7" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock37" DataContext="{Binding Points[34]}" Grid.Column="8" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock38" DataContext="{Binding Points[35]}" Grid.Column="9" Grid.Row="4" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock40" DataContext="{Binding Points[36]}" Grid.Column="1" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock41" DataContext="{Binding Points[37]}" Grid.Column="2" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock42" DataContext="{Binding Points[38]}" Grid.Column="3" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock43" DataContext="{Binding Points[39]}" Grid.Column="4" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock44" DataContext="{Binding Points[40]}" Grid.Column="5" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock45" DataContext="{Binding Points[41]}" Grid.Column="6" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock46" DataContext="{Binding Points[42]}" Grid.Column="7" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock47" DataContext="{Binding Points[43]}" Grid.Column="8" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock48" DataContext="{Binding Points[44]}" Grid.Column="9" Grid.Row="5" Style="{StaticResource textBlockStyle}"/>

        <TextBlock x:Name="textBlock50" DataContext="{Binding Points[45]}" Grid.Column="1" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock51" DataContext="{Binding Points[46]}" Grid.Column="2" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock52" DataContext="{Binding Points[47]}" Grid.Column="3" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock53" DataContext="{Binding Points[48]}" Grid.Column="4" Grid.Row="6" Style="{StaticResource textBlockStyle}"/> 
        <TextBlock x:Name="textBlock54" DataContext="{Binding Points[49]}" Grid.Column="5" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock55" DataContext="{Binding Points[50]}" Grid.Column="6" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock56" DataContext="{Binding Points[51]}" Grid.Column="7" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock57" DataContext="{Binding Points[52]}" Grid.Column="8" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock58" DataContext="{Binding Points[53]}" Grid.Column="9" Grid.Row="6" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock60" DataContext="{Binding Points[54]}" Grid.Column="1" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock61" DataContext="{Binding Points[55]}" Grid.Column="2" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock62" DataContext="{Binding Points[56]}" Grid.Column="3" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock63" DataContext="{Binding Points[57]}" Grid.Column="4" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock64" DataContext="{Binding Points[58]}" Grid.Column="5" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock65" DataContext="{Binding Points[59]}" Grid.Column="6" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock66" DataContext="{Binding Points[60]}" Grid.Column="7" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock67" DataContext="{Binding Points[61]}" Grid.Column="8" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock68" DataContext="{Binding Points[62]}" Grid.Column="9" Grid.Row="7" Style="{StaticResource textBlockStyle}"/>

        <TextBlock x:Name="textBlock70" DataContext="{Binding Points[63]}" Grid.Column="1" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock71" DataContext="{Binding Points[64]}" Grid.Column="2" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock72" DataContext="{Binding Points[65]}" Grid.Column="3" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock73" DataContext="{Binding Points[66]}" Grid.Column="4" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock74" DataContext="{Binding Points[67]}" Grid.Column="5" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock75" DataContext="{Binding Points[68]}" Grid.Column="6" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock76" DataContext="{Binding Points[69]}" Grid.Column="7" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock77" DataContext="{Binding Points[70]}" Grid.Column="8" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock78" DataContext="{Binding Points[71]}" Grid.Column="9" Grid.Row="8" Style="{StaticResource textBlockStyle}"/>

        <TextBlock x:Name="textBlock80" DataContext="{Binding Points[72]}" Grid.Column="1" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock81" DataContext="{Binding Points[73]}" Grid.Column="2" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock82" DataContext="{Binding Points[74]}" Grid.Column="3" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock83" DataContext="{Binding Points[75]}" Grid.Column="4" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock84" DataContext="{Binding Points[76]}" Grid.Column="5" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock85" DataContext="{Binding Points[77]}" Grid.Column="6" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock86" DataContext="{Binding Points[78]}" Grid.Column="7" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock87" DataContext="{Binding Points[79]}" Grid.Column="8" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>
        <TextBlock x:Name="textBlock88" DataContext="{Binding Points[80]}" Grid.Column="9" Grid.Row="9" Style="{StaticResource textBlockStyle}"/>

    </Grid>

en cuanto a la clase generadora del Sudoku, os dejo el código de generación, si alguien lo necesita, que no dude en solicitarme el proyecto que gustosamente se lo pasaré.

Os dejo un gif de como funciona. Un saludos amig@s

sudoku06.gif

Pd: El espacio de nombres no tiene nada que ver con los sudokus, pero a cada proyecto de visual studio, le asigno un nombre de un ilustre matemático o físico y en este caso le ha tocado a Ramanujan.

/* ****************************************************************
* © JOAQUIN MARTINEZ RUS 2016
* PROYECTO:        Clase generadora de Sudoku
* Archivo:         SudokuManager.cs
* Descripción:     Clase SudokuManager.
 *
* Historial:
*                  1. 09 sep 2016. Creación
*
* Comentarios:      Genera un sudoku mediante iteraciones erróneas
*
*
*******************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;

namespace Ramanujan
{
    public class SudokuManager : INotifyPropertyChanged
    {
        #region Constructor

        public SudokuManager()
        {
            Points = new List();
        }

        #endregion

        #region Properties and fields

        public List Points { get; set; }

        List initialCollection = new List();
        List selection = new List();
        public bool IsGood { get; set; }
        private int iterations;
        public int Iterations
        {
            get { return iterations; }
            set
            {
                iterations = value;
                OnPropertyChanged("Iterations");
            }
        }
        private double miliseconds;

        public double Miliseconds
        {
            get { return miliseconds; }
            set
            {
                miliseconds = value;
                OnPropertyChanged("Miliseconds");
                OnPropertyChanged("Points");
            }
        }

        #endregion

        #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string _PropertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(_PropertyName));
            }
        }

        #endregion

        #region Public methods

        public bool Start()
        {
            reset();
            Iterations++;
            foreach (var item in Points)
            {
                int number = setSelection(item);
                if (number != -1)
                {
                    item.Value = number;
                    item.IsVisible = true;
                }
                else
                {
                    IsGood = false;
                    return IsGood;
                }
            }
            IsGood = true;

            return IsGood;
        }

        public string ToString()
        {
            string textOut = "";
            foreach (var item in Points)
            {
                textOut += item.Value.ToString() + " ";
                if (item.YCoor == 8)
                {
                    textOut += "\n";
                }

            }
            return textOut;
        }

        #endregion

        #region Private Methods

        private void reset()
        {
            initialCollection.Clear();
            selection.Clear();
            Points.Clear();
            for (int i = 1; i < 10; i++)
            {
                initialCollection.Add(i);
            }

            setCoordinates();
        }

        private void setCoordinates()
        {
            for (int i = 0; i  Points.Where(k => k.YCoor == y).Select(k => k.Value).Contains(n));

            // eliminar los elementos cuya coordenada x sea igual que la del punto
            var xx = Points.Where(k => k.XCoor == x && k.Value != 0);
            selection.RemoveAll(n => Points.Where(k => k.XCoor == x).Select(k => k.Value).Contains(n));

            // eliminar los elementos del cuadrado
            var square = getFirstSquareNumber(Points.IndexOf(p));
            selection.RemoveAll(n => square.Contains(n));

            return getElection(p);
        }

        private KeyValuePair getProbability(int n, Point p)
        {
            int nColExistNumber = 0;
            for (int i = p.YCoor + 1; i  h.Value).First();
                    if (k == n)
                    {
                        nColExistNumber++;
                    }
                }
            }
            double casosTotales = 9 - p.XCoor;
            double probability = (casosTotales - nColExistNumber) / casosTotales;
            return new KeyValuePair(n, probability);
        }

        private int getElection(Point p)
        {
            List values = new List();
            foreach (var item in selection)
            {
                values.Add(getProbability(item, p));

            }

            List candidatosTemporales = new List();
            foreach (var item in values)
            {
                if (item.Value == 1)
                {
                    candidatosTemporales.Add(item.Key);
                }
                else
                {
                    for (int i = 0; i  0 ? candidatosTemporales[index] : -1;
            return r;
        }

        private List getFirstSquareNumber(int index)
        {
            List numbers = new List();
            int restoX = index % 3;
            int multiplo = (index / 9) % 3;
            int first = index - restoX - 9 * multiplo;
            int nextIndex = 0;

            for (int i = 0; i < 3; i++)
            {
                for (int j = 0; j < 3; j++)
                {
                    nextIndex = first + i * 9 + j;
                    if (Points[nextIndex].Value != 0)
                    {
                        numbers.Add(Points[nextIndex].Value);
                    }
                }
            }
            return numbers;
        }

        #endregion

    }

    public class Point:INotifyPropertyChanged
    {
        public Point(int x, int y)
        {
            XCoor = x;
            YCoor = y;
        }

        public int XCoor { get; set; }
        public int YCoor { get; set; }
        public int Value { get; set; }
        private bool isVisible=true;

        public bool IsVisible
        {
            get { return isVisible; }
            set
            {
                isVisible = value;
                OnPropertyChanged("IsVisible");
            }
        }

        public int Square
        {
            get
            {
                int x = XCoor % 3;
                int y = YCoor % 3;
                return y + 3 * x ;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string _PropertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(_PropertyName));
            }
        }

    }
}
Anuncios

WPF y la clase canina

WPF y la clase canina
Hoy el post va de perros.He querido mostrar varios aspectos que voy a exponer:

  • Un clase ViewModel. (La clase Dog)
  • Incluir hilos (Task) en la clase
  • Una ventana diseñada con XAML con una sola línea de código InitializeComponent();de modo que el código XAML debe entenderse con la clase (ViewModel) y deben actualizarse sus controles a pesar de que estén corriendo los hilos.
  • Introducción a los Converter

baloo03

Para mostraros esto de un modo más original, he pensado en mi perro Baloo, el cual he pensado muchas veces como actúa, la sencillez con la que toma decisiones y como no, he diseñado un perro con un aparato digestivo de lo más sencillo, (mi perro es bastante más complejo y no defeca u orina en cualquier sitio, mi trabajo me ha costado). Hoy he leido una noticia que comentaba que los perros nos entendían las palabras y frases, por lo que quiero decir que si algún perro lee esto, no se sienta ofendido por mis palabras en las que no vanaglorio su intelectualidad; rompiendo una lanza en favor de los perros, tengo que decir que a veces demuestran más sentido común que los seres humanos, pero como siempre, me centraré en el post!

El diseño consta de dos clases, la clase Dog que hará as funciones de ViewModel, la clase BoolToVisibleConverter que es un converter y un ventana para mostrar la información diseñada integramente con XAML. La aplicación no necesita interacción del usuario, es decir , los parámetros varían con el tiempo tomando algunos de ellos aleatoriamente.

La clase Dog implementa por supuesto la interfaz INotifyPropertyChanged y tiene los siguientes métodos y propiedades:

Baloo01.png

Los métodos de los que consta la clase son las cosas normales que realiza un perro y cada vez que realiza algunas de estas acciones varían las propiedades que dependiendo de su estado, ejecutarán otra acción. Las principales propiedades son Energia, NivelEstomago, NivelVejiga y NivelIntestino.

Según un intervalo, según pasa el tiempo, la energía se va consumiendo, por tanto cuando tiene bajos los niveles de energía, tiene hambre o el nivel estomacal es bajo, come, cuando tiene el estómago lleno, hace la digestión, cuando hace la digestión llena el intestino y aumenta la energía, posteriormente lo vacía haciendo caquitas, cuando tiene el nivel de energía lleno corre, si corre baja la energía más rápido y tiene sed, cuando llena la vejiga hace pipí, si se queda sin energía duerme hasta llenar el nivel de esta y así indefinidamente, es decir un sistema retroalimentado como pueda ser un sistema digestivo. Os muestro un video donde podréis verlo más claro.

Creo que lo de menos es como funciona el sistema, porque lo que importa es que cada método tiene un intervalo propio, (no se tarda lo mismo en hacer la digestión que en hacer pipí) y si no se creara un hilo al iniciar el método, la aplicación quedaría bloqueada y no podríamos moverla o alterar sus controles, así que cada vez que se ejecuta un método, se crea un nuevo Task.Factory.StarNew(()=>Metodo()), pudiendo continuar el sistema digestivo con sus cosas. Usando este código y sin que la clase toque una sola línea de la clase de la ventana, tenemos asegurado un funcionamiento adecuado.

        private void Comer(int cantidad)
        {
            EstaComiendo = true;
            for (int i = 0; i < cantidad; i++)
            {
                NivelEstomago ++;
                Thread.Sleep(IncComer);
                // si el perro está lleno, no come más!
                if (NivelEstomago >= CapacidadEstomago)
                {
                    NivelEstomago = CapacidadEstomago;
                    break;
                }
            }

            Task.Factory.StartNew(() => hacerDigestion());
            EstaComiendo = false;
        }

La clase es instanciada desde el código XAML, así que el constructor inicia el método Vivir y este arrastra al resto de métodos. Para instanciar una clase desde el código XAML, debemos crearla en Window.Resources o en Grid.Resources para que luego el DataContext pueda ser asignado con la key de la clase y además al instanciarlo, podemos asignar el valor a las propiedades públicas, lo muestro

    <Window.Resources>
        <local:BoolToVisibleConverter x:Key="b2vc">
        <local:Dog x:Key="dog" Nombre="Baloo" Raza="Bodeguero">
    <Window.Resources>
    <Grid DataContext="{StaticResource dog}">

Por otra parte, el código XAML, una vez que se ha instaciado la clase, solo queda hacer binding a las propiedades que deseamos actualizar, las cuales deben llamar al evento de cambio de propiedad, cada vez que se produzca un cambio, en caso contrario, no se mostrarán datos

        public int NivelEstomago
        {
            get { return nivelEstomago; }
            set
            {
                nivelEstomago = value < 0 ? 0 : value;
                if (NivelEstomago== CapacidadEstomago)
                {
                    Task.Factory.StartNew(() => hacerDigestion());
                }
                OnPropertyChanged("NivelEstomago");
            }
        }

he aquí un ejemplo del enlace de datos desde la propiedad NivelEstomago al valor del control ProgressBar que mostrará como de lleno esté el estómago 😉

<ProgressBar x:Name="PBEstomago" Grid.Column="2" Value="{Binding NivelEstomago}" Style="{StaticResource progressbarStyle}"/>

Por último, voy a hacer referencia a los converter. WPF tiene multitud de converter, por ejemplo, cuando escribimos True en una propiedad, WPF está convirtiendo este valor en un valor booleano, o cuando le damos un color a la propiedad Background, lo que se plasma en el código XAML es un texto, sin embargo este se convertirá en un objeto Brush o SolidColorBrush o lo que corresponda. Hay veces que necesitaremos convertir valores booleanos en un color, o por ejemplo, como es el caso que voy a exponer, quiero variar la visibilidad de un control mediante un valor booleano como antiguamente, visible=true o false, pero en WPF la visibilidad de un control tiene diferentes valores (por cierto a alguien se le tenía que haber ocurrido antes), bueno pues mediante valores booleano procedentes de las propiedades de nuestra clase Dog, convertiremos un control en visible por true u oculto con false. Para hacer esto, lo primero debemos indicar en los resources es el camino a la clase que contiene el converter, caso que podemos ver en el código de más arriba, cuando instanciábamos la clase y le dimos como valor de key “b2vc”

<local:BoolToVisibleConverter x:Key="b2vc">

en el binding debemos indicar el path desde donde se alimentará el control y la clase conversor. Esta clase debe implementar la interfaz IValueConverter, la cual implementa dos métodos, Convert y ConvertBack el primero es usado para convertir los datos desde la clase al control y el segundo para convertir los datos del control hacia la clase siempre que tengamos habilitada esta opción (ver Mode=TwoWay). Yo he creado dos propiedades en la clase, para poder usar valores booelanos que funcionen al contrario, con true se oculten y con false se muestren. En estos métodos, e pasa el valor a convertir, el tipo, parámetros y la cultura, pero lo normal es que solo se pase valor a convertir, (es bueno tener ases en la manga).

Si tenéis cualquier duda, dejáis un comentario. Espero que hayáis disfrutado con la clase canina. A continuación os dejo el código completo o el proyecto como gustéis.

Saludos

Clase Dog

/* ****************************************************************
* © JOAQUIN MARTINEZ RUS 2016
* PROYECTO:        Sistema digestivo canino
* Archivo:         Dog.cs
* Descripción:     Clase Dog.
 *
* Historial:
*                  1. 31 ago 2016. Creación
*
* Comentarios:
*
*
*******************************************************************/
using System;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.ComponentModel;

namespace DogsNameSpace
{
    public class Dog: INotifyPropertyChanged
    {
        public Dog()
        {
            Task.Factory.StartNew(() => Vivir());
        }

        #region Eventos

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Propiedades
        // SOLO LECTURA
        public double MaxEnergia { get; } = 100;
        public int CapacidadEstomago { get; } = 80;
        public int CapacidadVejiga { get; } = 60;
        public int CapacidadInstentinoGrueso { get; } = 80;

        // VARIABLES PRIVADAS
        private int energia = 23;
        private int nivelIntestino = 0;
        private int nivelVejiga=22;
        private int nivelEstomago=25;
        private int incEnergia = 3000;

        // PÚBLICAS
        public string Nombre { get; set; }
        public string Raza { get; set; }
        public bool EnergiaEstaDisminuyendo { get; set; }

        private bool estaComiendo;
        private bool estaHaciendoCaca;
        private bool estaHaciendoPipi;
        private bool estaBebiendo;
        private bool estaHaciendoDigestion;
        private bool estaLadrando;
        private bool estaCorriendo;
        private bool estaDurmiendo;

        public bool EstaDurmiendo
        {
            get { return estaDurmiendo; }
            set
            {
                estaDurmiendo = value;
                OnPropertyChanged("EstaDurmiendo");
            }
        }
        public bool EstaComiendo
        {
            get { return estaComiendo; }
            set
            {
                estaComiendo = value;
                OnPropertyChanged("EstaComiendo");
            }
        }
        public bool EstaHaciendoCaca
        {
            get { return estaHaciendoCaca; }
            set
            {
                estaHaciendoCaca = value;
                OnPropertyChanged("EstaHaciendoCaca");
            }
        }
        public bool EstaHaciendoPipi
        {
            get { return estaHaciendoPipi; }
            set
            {
                estaHaciendoPipi = value;
                OnPropertyChanged("EstaHaciendoPipi");
            }
        }
        public bool EstaBebiendo
        {
            get { return estaBebiendo; }
            set
            {
                estaBebiendo = value;
                OnPropertyChanged("EstaBebiendo");
            }
        }
        public bool EstaHaciendoDigestion
        {
            get { return estaHaciendoDigestion; }
            set
            {
                estaHaciendoDigestion = value;
                OnPropertyChanged("EstaHaciendoDigestion");
            }
        }
        public bool EstaLadrando
        {
            get { return estaLadrando; }
            set
            {
                estaLadrando = value;
                OnPropertyChanged("EstaLadrando");
            }
        }
        public bool EstaCorriendo
        {
            get { return estaCorriendo; }
            set
            {
                estaCorriendo = value;
                OnPropertyChanged("EstaCorriendo");
            }
        }

        public int NivelEstomago
        {
            get { return nivelEstomago; }
            set
            {
                nivelEstomago = value < 0 ? 0 : value;
                if (NivelEstomago== CapacidadEstomago)
                {
                    Task.Factory.StartNew(() => hacerDigestion());
                }
                OnPropertyChanged("NivelEstomago");
            }
        }
        public int Energia
        {
            get { return energia; }
            set
            {
                energia = value < 0 ? 0 : value;

                if (EnergiaEstaDisminuyendo)
                {
                    switch (energia)
                    {
                        case 0:
                            Task.Factory.StartNew(() => Dormir());
                            break;
                        case 20:
                            // comer
                            Ladrar(5);
                            Random rnd = new Random();
                            int cant = rnd.Next(1, 100);
                            Task.Factory.StartNew(() => Comer(cant));

                            break;
                        case 30:
                            // beber
                            Ladrar(2);
                            Random rnd1 = new Random();
                            int cantAgua = rnd1.Next(1, 30);
                            Task.Factory.StartNew(() => Beber(cantAgua));
                            break;

                    }

                }
                else if (energia==100 && !EstaCorriendo)
                {
                    Ladrar(5);
                    Random rnd2 = new Random();
                    int tiempo = rnd2.Next(1, 30);
                    Task.Factory.StartNew(() => Correr(tiempo));
                }

                OnPropertyChanged("Energia");
            }
        }
        public int NivelIntestino
        {
            get { return nivelIntestino; }
            set
            {
                nivelIntestino = value < 0 ? 0 : value;
                if (nivelIntestino >= CapacidadInstentinoGrueso)
                {
                    Task.Factory.StartNew(() => HacerCaca());
                }
                OnPropertyChanged("NivelIntestino");
            }
        }
        public int NivelVejiga        {
            get { return nivelVejiga; }
            set
            {
                nivelVejiga = value<0?0:value;

                if (nivelVejiga>=CapacidadVejiga)
                {
                    Task.Factory.StartNew(() => HacerPipi());
                }
                OnPropertyChanged("NivelVejiga");
            }
        }

        public int IncEnergia
        {
            get { return incEnergia; }
            set
            {
                incEnergia = value;
                OnPropertyChanged("IncEnergia");
            }
        }
        public int IncComer { get { return (int)(IncEnergia * 0.16); } }
        public int IncBeber { get { return (int)(IncEnergia * 0.08); } }
        public int IncHacerCaca { get { return (int)(IncEnergia * 0.03); } }
        public int IncHacerPipi { get { return (int)(IncEnergia * 0.03); } }
        public int IncLadrar { get { return (int)(IncEnergia * 0.015); } }
        public int IncCorrer { get { return (int)(IncEnergia * 0.33); } }
        public int IncDigestion { get { return (int)(IncEnergia * 0.16); } }
        public int IncDormir { get { return (int)(IncEnergia * 0.16); } }

        #endregion

        /// <summary>
        /// Inicia el evento de cambio de propiedad
        /// </summary>
        /// <param name="p_PropertyName">Nombre de la propiedad</param>
        public void OnPropertyChanged(string p_PropertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(p_PropertyName));
            }
        }        

        /// <summary>
        /// Método inicial.
        /// </summary>
        private void Vivir()
        {
            do
            {
                Energia--;
                EnergiaEstaDisminuyendo = true;
                Thread.Sleep(IncEnergia);

            } while (true);
        }

        private void Dormir()
        {
            EnergiaEstaDisminuyendo = false;
            for (int i = 0; i < MaxEnergia; i++)
            {
                Energia++;
                Thread.Sleep(IncDormir);
            }
        }

        /// <summary>
        /// Realiza un número de ladridos determinado
        /// </summary>
        /// <param name="numLadridos">Número de ladridos a realizar</param>
        /// <returns>Retorna una cadena de texto con los ladridos</returns>
        private string Ladrar(int numLadridos)
        {
            StringBuilder ladridos = new StringBuilder();
            EstaLadrando = true;

            for (int i = 0; i < numLadridos; i++)
            {
                ladridos.Append("Guau ");
                Thread.Sleep(IncLadrar);
            }

            EstaLadrando = false;

            return ladridos.ToString();
        }

        /// <summary>
        /// Realiza un ladrido
        /// </summary>
        /// <returns>Retorna una cadena de texto con un ladrido</returns>
        private string Ladrar()
        {
            return Ladrar(1);
        }

        private void Correr(int tiempo)
        {
            EstaCorriendo = true;
            for (int i = 0; i < tiempo; i++)
            {
                Energia--;
                EnergiaEstaDisminuyendo = true;
                Thread.Sleep(IncCorrer);

                // si lleva más de 15 corriendo, bebe agua
                if (i > 15)
                {
                    Beber(30);
                }

            }

            EstaCorriendo = false;
        }

        private void Comer(int cantidad)
        {
            EstaComiendo = true;
            for (int i = 0; i < cantidad; i++)
            {
                NivelEstomago ++;
                Thread.Sleep(IncComer);

                // si el perro está lleno, no come más!
                if (NivelEstomago >= CapacidadEstomago)
                {
                    NivelEstomago = CapacidadEstomago;

                    break;
                }
            }

            Task.Factory.StartNew(() => hacerDigestion());
            EstaComiendo = false;

        }

        private void Beber(int cantidad)
        {
            EstaBebiendo = true;

            int nivelInicial = NivelVejiga;

            for (int i = 0; i < nivelInicial; i++)
            {
                NivelVejiga++;
                Thread.Sleep(IncBeber);
            }
            EstaBebiendo = false;
        }

        private void HacerCaca()
        {
            EstaHaciendoCaca = true;
            // vacia el intestino
            int nivelInicial = NivelIntestino;
            for (int i = 0; i < nivelInicial; i++)
            {
                NivelIntestino--;
                Thread.Sleep(IncHacerCaca);
            }

            Ladrar();

            EstaHaciendoCaca = false;

        }

        private void HacerPipi()
        {
            EstaHaciendoPipi = true;
            int nivelInicial = NivelVejiga;

            for (int i = 0; i < nivelInicial; i++)
            {
                NivelVejiga--;
                Thread.Sleep(IncHacerPipi);
            }
            Ladrar();

            EstaHaciendoPipi = false;

        }

        private void hacerDigestion()
        {
            EstaHaciendoDigestion = true;
            int nivelInicial = NivelEstomago;
            for (int i = 0; i < nivelInicial; i++)
            {
                NivelEstomago--;
                EnergiaEstaDisminuyendo = false;
                if (Energia<100)
                {
                    Energia++;
                }
                else
                {
                    break;
                }
                Thread.Sleep(IncDigestion);
            }

            // Llenar intestino
            double masaConvertida = nivelInicial * .2;
            for (int i = 0; i < (nivelInicial - (int)masaConvertida); i++)
            {
                NivelIntestino++;
                Thread.Sleep(20);
            }

            EstaHaciendoDigestion = false;
        }

    }
}

Clase BoolToVisibleConverter

using System;
using System.Windows;
using System.Windows.Data;

namespace DogsNameSpace
{
    public class BoolToVisibleConverter : IValueConverter
    {
        #region Constructor

        public BoolToVisibleConverter() { }

        #endregion

        #region Propiedades

        public bool Collapse { get; set; }
        public bool Reverse { get; set; }

        #endregion

        #region IValueConverter

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool bValue = (bool)value;

            if (bValue != Reverse)
            {
                return Visibility.Visible;
            }
            else
            {
                if (Collapse)
                    return Visibility.Collapsed;
                else
                    return Visibility.Hidden;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            Visibility visibility = (Visibility)value;

            if (visibility == Visibility.Visible)
                return !Reverse;
            else
                return Reverse;
        }

        #endregion
    }
}

Código XAML

<Window x:Class="DogsNameSpace.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DogsNameSpace"
        mc:Ignorable="d"
        Title="Sistema digestivo canino" Height="600" Width="700" >
    <Window.Resources>
        <local:BoolToVisibleConverter x:Key="b2vc"/>
        <local:Dog x:Key="dog" Nombre="Baloo" Raza="Bodeguero"/>
    </Window.Resources>
    <Grid DataContext="{StaticResource dog}">
        <Grid.RowDefinitions>
            <RowDefinition Height="273"/>
            <RowDefinition Height="41*"/>
            <RowDefinition Height="135*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="250"/>
            <ColumnDefinition Width="80"/>
            <ColumnDefinition Width="80"/>
            <ColumnDefinition Width="80"/>
            <ColumnDefinition Width="80"/>
            <ColumnDefinition Width="80*"/>
        </Grid.ColumnDefinitions>
        <StackPanel HorizontalAlignment="Left">
            <TextBlock Text="{Binding Nombre}" Style="{StaticResource textblockStyle1}"/>
            <TextBlock Text="{Binding Raza}" Style="{StaticResource textblockStyle1}"/>
        </StackPanel>
        <Image x:Name="BalooRight_png" Source="BalooRight.png" Stretch="Uniform"/>
        <TextBlock Grid.Column="1" Text="{Binding Energia}" Style="{StaticResource textblockPorcStyle1}"/>
        <ProgressBar x:Name="PBEnergia"  Grid.Column="1" Value="{Binding Energia}" Style="{StaticResource progressbarStyle}"/>
        <TextBlock Grid.Column="2" Text="{Binding NivelEstomago}" Style="{StaticResource textblockPorcStyle1}"/>
        <ProgressBar x:Name="PBEstomago" Grid.Column="2" Value="{Binding NivelEstomago}" Style="{StaticResource progressbarStyle}"/>
        <TextBlock Grid.Column="3" Text="{Binding NivelVejiga}" Style="{StaticResource textblockPorcStyle1}"/>
        <ProgressBar x:Name="PBIntestino" Grid.Column="3" Value="{Binding NivelVejiga}" Style="{StaticResource progressbarStyle}"/>
        <TextBlock Grid.Column="4" Text="{Binding NivelIntestino}" Style="{StaticResource textblockPorcStyle1}"/>
        <ProgressBar x:Name="PBVejiga" Grid.Column="4" Value="{Binding NivelIntestino}" Style="{StaticResource progressbarStyle}"/>
        <TextBlock Grid.Column="5" Text="{Binding IncEnergia}" Style="{StaticResource textblockPorcStyle1}"/>

        <TextBlock x:Name="textBlockEnergia" Style="{StaticResource textblockStyle}" Grid.Column="1" Grid.Row="1" Text="Energía" />
        <TextBlock x:Name="textBlockEstomago" Style="{StaticResource textblockStyle}" Grid.Column="2"  Grid.Row="1" Text="Estómago" />
        <TextBlock x:Name="textBlockVejiga" Style="{StaticResource textblockStyle}" Grid.Column="3" Grid.Row="1" Text="Vejiga" />
        <TextBlock x:Name="textBlockIntestino" Style="{StaticResource textblockStyle}" Grid.Column="4" Grid.Row="1" Text="Intestino" />
        <TextBlock x:Name="textBlockTime" Style="{StaticResource textblockStyle}" Grid.Column="5" Grid.Row="1" Text="Intervalo (ms)" />

        <StackPanel Grid.Column="0" Grid.Row="2">

            <TextBlock Text="Durmiendo" Style="{StaticResource textblockStyle1}" Visibility="Collapsed"/>
            <TextBlock Text="Haciendo la digestión" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaHaciendoDigestion,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Ladrando" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaLadrando,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Comiendo" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaComiendo,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Bebiendo" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaBebiendo,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Corriendo" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaCorriendo,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Haciendo Caca" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaHaciendoCaca,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Haciendo Pipí" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaHaciendoPipi,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Durmiendo" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaDurmiendo,Converter={StaticResource b2vc}}"/>
        </StackPanel>
        <Slider x:Name="slider" Grid.Column="5" Height="200"  Value="{Binding IncEnergia}" VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="15" Orientation="Vertical" Minimum="1000" Maximum="5000" SmallChange="100" TickFrequency="500"/>
    </Grid>
</Window>

Cartón de bingo

Cartón de bingo

Hoy toca la composición y como no teclear código con WPF.Digo esto porque no pretendo explicar las diferencias entre composición y agregación, lo que está claro es que la composición quiere decir que se compone, un coche se compone de motor, ruedas, chasis, etc y el coche no es nada sin estas piezas y estas piezas, podrían serlo por su cuenta, pero fueron diseñadas para funcionar en el coche, por tanto aplicándolo al cartón de bingo (que no tiene nada que ver con el coche en cuanto a fisonomía pero si a composición), este se compone de líneas y cada línea de números. Los números tienen aplicación en la vida, pero un cartón sin estos poco valdría y por supuesto un cartón sin líneas, ¿que cantaríamos? números! no, el bingo tiene sus reglas y ahí estamos los programadores para aplicar las reglas al software y llevarlas a cabo.

Si realizamos un diagrama de clases (simplificado) de un cartón de bingo, sería algo como esto

bingo01

La línea que une las clases o la asociación con el rombo relleno de negro, indica eso, que es una composición.

Sabiendo esto, vamos a diseñar un cartón de bingo más en serio y crearemos

bingo02.png

Esto es lo que se me ha ocurrido (ahora solo el diagrama), un objeto Number, el cual tiene dos propiedades, una el valor y otra si el número está marcado, un objeto Row (Línea) que contiene una colección de números y un objeto Card que contiene una colección de líneas. Por partes.

La clase Number, como ya he dicho tiene dos propiedades y además tiene dos constructores y un solo método; los constructores uno sin parámetros y otro para asignarle el número. Como la clase implementa la interfaz Icomparable<Number>, obligatoriamente el método CompareTo debemos crearlo para que posteriormente pueda ordenarse la lista de números como nosotros queremos. Y visto esto, aquí el código

    public class Number:IComparable<Number>
    {
        #region Constructor

        /// <summary>
        /// Representa un número de cartón de bingo
        /// </summary>
        public Number() { }

        /// <summary>
        /// Representa un número de cartón de bingo
        /// </summary>
        /// <param name="number">Número que se asigna como valor</param>
        public Number(int number)
        {
            this.Value = number;
        }

        #endregion

        #region Fields, properties and constants

        /// <summary>
        /// Valor del número
        /// </summary>
        public int Value { get; set; }

        /// <summary>
        /// Indica si el número está marcado o no
        /// </summary>
        public bool IsChecked { get; set; } = false;
        
        #endregion

        #region Public Methods

        /// <summary>
        /// Compara esta instancia con un objeto específico y retorna un entero que indica
        /// </summary>
        /// <param name="other">Objeto a comparar</param>
        /// <returns>Retorna un entero con signo. Negativo si es menor, un 0 si es igual y un entero positivo si es mayor.</returns>
        public int CompareTo(Number other)
        {
            int result = 0;
            if (this.Value > other.Value)
            {
                result = 1;
            }
            else if (this.Value < other.Value)
            {
                result = -1;
            }
            return result;
        }

        #endregion
    }

la clase Row o la clase de la línea le he incluido dos constructores, uno que que genera una línea en base a una lista de números disponibles, esto es porque a la línea le interesa saber que números hay que escoger para que no se repitan y el otro al que le pasamos los números en un array como parámetro.

Tiene una constante que indica la cantidad de números que tiene una línea y una colección ¿de que?, pues de la clase Number. El motor de la clase o los métodos privados, son dos, uno para obtener una lista de decenas y otro para crear la línea con sus números, los cuales no deben repetir decena ni repetir números de otras líneas. Para esto creo una lista de las decenas, 0, 10, 20, … 70 y 80, instancio la clase Random con una semilla sobre la suma de los milisegundos del tiempo actual y las decenas pendientes, esto provoca una semilla distinta en cada iteración y por tanto algo más parecido a la aleatoriedad, además le incluyo un número impar de milisegundos de retardo. Esto también nos va a generar cartones más reales y con una distribución más normal, imaginad un cartón con los 15 números agrupados desde las unidades a la columna de 40 y el resto de columnas (50, 60, 70 y 80) vacías, esos cartones son válidos, pero poca gente los querría, es como si nos dieran un billete de lotería con el 11111, está en el bombo, pero poca gente lo quiere. Después de esto,

  • Sobre la lista de decenas escojo una al azar,
  • Filtro los números disponibles para acotarlos en esa decena y vuelvo a escoger uno aleatorio
  • Elimino el número escogido de la lista de números disponibles (para que en el siguiente número no lo esté)
  • Añado el número a la colección de la línea
  • Ordeno la línea.

con esto, cada vez que genero una línea, me aseguro que no se repitan las decenas y los números por línea.

Los métodos públicos, son:

  • ToString(). Para devolver una cadena con los números de la línea, sustituyendo los espacios por asteriscos (este lo he ppuesto por si alguien lo quiere obtener por consola)
  • GetRowNumbersToString(). Este igual que el anterior, pero con la diferencia de que solo aparecen los números separados por tabulaciones (Cabe destacar el uso de ForEach en una sola línea)
  • GetRowNumber(). Que devuelve una lista de objetos Number con todos los números de la línea.

El código…

    public class Row
    {
        #region Constructor

        /// <summary>
        /// Representa una línea de cartón de bingo
        /// </summary>
        /// <param name="_availableNumbers">Lista de números disponibles desde la cual se generará los objetos Number</param>
        public Row(List<int> _availableNumbers)
        {
            CreateRow(_availableNumbers);
        }

        /// <summary>
        /// Representa una línea de cartón de bingo
        /// </summary>
        /// <param name="args">Array con los números que se usarán para generar los objetos Number</param>
        public Row(int[] args)
        {
            if (args.Length > numbersCount) throw new OverflowException(String.Format("El número máximo de números que acepta una línea es de {0}", numbersCount));
            foreach (var item in args)
            {
                Number number = new Number();
                number.Value = item;
                rowNumbers.Add(number);
            }

            rowNumbers.Sort();
        }

        #endregion

        #region Fields, properties and constants

        /// <summary>
        /// Constante que indica la cantida de números de una linea
        /// </summary>
        private const int numbersCount = 5;

        /// <summary>
        /// Lista de objetos Number
        /// </summary>
        public List<Number> rowNumbers = new List<Number>();

        #endregion

        #region Public Methods

        /// <summary>
        /// Retorna una cadena formateada por decenas con los valores de los objetos Number contenidos en esta instancia
        /// </summary>
        /// <returns>Cadena de texto con los valores de los objetos Number</returns>
        public override string ToString()
        {
            string textToString = "";

            for (int i = 0; i < 9; i++)
            {
                int decena = i * 10;
                int incremento = i == 8 ? decena + 11 : decena + 10;
                var query = this.rowNumbers.Where(k => k.Value > decena && k.Value < incremento);

                if (query.Count() > 0)
                {
                    textToString += query.First().Value + "\t";
                }
                else
                {
                    textToString += "*\t";
                }

            }

            return textToString;
        }

        /// <summary>
        /// Retorna una cadena con los valores  de los objetos Number contenidos en esta instancia
        /// </summary>
        /// <returns>Cadena de texto con los valores de los objetos Number</returns>
        public string GetRowNumbersToString()
        {
            string textToString = "";
            this.rowNumbers.ForEach(k => textToString += k.Value + "\t");
            return textToString;
        }

        public List<Number> GetRowNumber()
        {
            List<Number> numbers = new List<Number>();

            for (int i = 0; i < 9; i++)
            {
                int decena = i * 10;
                int incremento = i == 8 ? decena + 11 : decena + 10;
                var query = this.rowNumbers.Where(k => k.Value > decena && k.Value < incremento);

                if (query.Count() > 0)
                {
                    numbers.Add(query.First());
                }
                else
                {
                    numbers.Add(new Number(-1));
                }

            }

            return numbers;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Inicia la instancia actual con objetos Number a partir de una lista de números disponibles
        /// </summary>
        /// <param name="_availableNumbers">Lista de números disponibles desde la cual se generará los objetos Number</param>
        private void CreateRow(List<int> _availableNumbers)
        {
            List<int> decenas = GetTen();
            int number = 0;
            Random rnd=new Random(DateTime.Now.Millisecond + _availableNumbers.Count);
            for (int i = 0; i < numbersCount; i++)
            {
                System.Threading.Thread.Sleep(3);
                int decena = decenas[rnd.Next(0, decenas.Count)];
                
                // Eliminar la decena de la lista para no solicitarla en la próxima petición
                decenas.Remove(decena);

                int incremento = decena == 80 ? decena + 11 : decena + 10;
                var temp = _availableNumbers.Where(k => k > decena && k < incremento);
                number = temp.ElementAt(rnd.Next(0, temp.Count()));
                _availableNumbers.Remove(number);

                rowNumbers.Add(new Number(number));
            }

            this.rowNumbers.Sort();
        }

        /// <summary>
        /// Retorna una lista con las decenas desde 0 al 80
        /// </summary>
        /// <returns></returns>
        private List<int> GetTen()
        {
            List<int> decenas = new List<int>();
            for (int i = 0; i < 9; i++)
            {
                decenas.Add(i*10);
            }
            return decenas;
        }

        #endregion
    }

Por último, la clase Card (Cartón, no ibas a pensar que algo tan sencillo como un cartón iba a tener tanta complejidad). Esta realiza lo siguiente:

  • Rellena la lista de números disponibles, los números del 1 al 90.
  • Crea un nuevo cartón, el cual instancia tres veces la clase Row,envíandole la lista de números disponibles, si en la primera línea se han añadido el 7, 23, 35,67,77 y 89, estos números una vez que se ha creado la fila, se eliminan de la lista de disponibilidad, esto lo hago para seleccionar solo los números que quedan y aumentar el rendimiento; os imagináis escoger un número del 1 al 90 y solo queda el número 43, hasta que acierte la aplicación puede pasar un tiempo no computado, mientras que si vamos extrayendo números, el tiempo siempre será el mismo
  • Llena una lista de cadenas de texto con los números del cartón con el fin de usarlos para la visualización.

Además, he implementado la interfaz IEquatable<Card> para en el futuro por si hubiera que comprobar un cartón, poder hacerlo comparando el cartón ganador. el código de la clase lo muestro a continuación (si teneís alguna duda con el código, no dudéis en preguntame y añadir un comentario, lo responderé gustosamente)

    public class Card: IEquatable<Card>,INotifyPropertyChanged
    {
        #region Constructor

        /// <summary>
        /// Representa un cartón de bingo con 3 líneas y 5 números por línea
        /// </summary>
        public Card()
        {
            FillAvailableNumbers();
            CreateNewCard();
            cardNumbers = new List<string>();

            foreach (var item in GetRowNumber().Select(k => k.Value))
            {
                cardNumbers.Add(item==-1?"":item.ToString());
            }
        }

        #endregion

        #region Fields, properties and constants

        /// <summary>
        /// Número de líneas que alberga un cartón
        /// </summary>
        private const int rowCount = 3;

        /// <summary>
        /// Lista de Líneas del cartón
        /// </summary>
        List<Row> cardRows = new List<Row>();

        /// <summary>
        /// Lista de números disponibles que se pueden agregar a un cartón 
        /// </summary>
        List<int> availableNumbers = new List<int>();

        /// <summary>
        /// Listado de número ordenados por filas para visualización
        /// </summary>
        public List<string> cardNumbers { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        public int CardNumber { get; set; }
        public int SerialNumber { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Retorna una cadena con los datos del cartón formateados
        /// </summary>
        /// <returns>Cadena detexto con los datos del cartón</returns>
        public override string ToString()
        {
            string textToString = "";

            foreach (var row in this.cardRows)
            {
                textToString += row.ToString() + "\n";
            }

            textToString += "\n";
            foreach (var row in this.cardRows)
            {
                textToString += row.GetRowNumbersToString() + "\n";
            }

            return textToString;
        }

        /// <summary>
        /// Retorna un valor que indica que esta instancia es igual a un objeto específico
        /// </summary>
        /// <param name="other">Un objeto Card a comparar con esta instancia</param>
        /// <returns>True si tiene el mismo valor que esta instancia; en caso contrario false</returns>
        public bool Equals(Card other)
        {
            bool isEquals = true;
            List<int> cardNumbersThis = GetCardNumbers(this);
            List<int> cardNumbersOther = GetCardNumbers(other);

            cardNumbersThis.ForEach(k => cardNumbersOther.Remove(k));

            if (cardNumbersOther.Count > 0) isEquals = false;

            return isEquals;
        }

        /// <summary>
        /// Retorna una lista con los números ordenados de cada fila y asignados los huecos con el valor -1
        /// </summary>
        /// <returns></returns>
        public List<Number> GetRowNumber()
        {
            List<Number> numbers = new List<Number>();

            foreach (var row in this.cardRows)
            {
                numbers.AddRange(row.GetRowNumber());
            }

            return numbers;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Retorna una lista de todos los números del cartón, indicando los elementos vacíos con -1
        /// </summary>
        /// <param name="card">Cartón de bingo del que se extraen los datos</param>
        /// <returns>Lista de enteros con todos los números del cartón. A los elementos vacíos, se les asigna -1</returns>
        private List<int> GetCardNumbers(Card card)
        {
            List<int> cardNumbers = new List<int>();

            foreach (var row in card.cardRows)
            {
                cardNumbers.AddRange(row.rowNumbers.Select(k => k.Value));
            }

            return cardNumbers;
        }

        /// <summary>
        /// crea las tres líneas y las asigna al cartón
        /// </summary>
        private void CreateNewCard()
        {

            for (int i = 0; i < rowCount; i++)
            {
                Row row = new Row(availableNumbers);

                foreach (var item in row.rowNumbers)
                {
                    availableNumbers.Remove(item.Value);
                }

                cardRows.Add(row);
            }
        }

        /// <summary>
        /// Rellena los números disponibles para generar cartones
        /// </summary>
        private void FillAvailableNumbers()
        {
            for (int i = 1; i < 91; i++)
            {
                availableNumbers.Add(i);
            }
        }

        #endregion

    }

Y ahora  vamos a diseñar el cartón con WPF sin escribir código C#, XAML si, pero este se encarga de los errores y la robustez.

Creo un control de usuario y le añado lo siguiente

<UserControl x:Class="Bingo.CardForm"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Pascal"
             mc:Ignorable="d" Height="250" Width="650">
    <Grid x:Name="CardGrid">
        <Grid.Resources>
            <Style x:Key="baseStyle" TargetType="TextBlock">
                <Setter Property="TextAlignment" Value="Center"/>
                <Setter Property="VerticalAlignment" Value="Stretch"/>
                <Setter Property="HorizontalAlignment" Value="Stretch"/>
                <Setter Property="Margin" Value="3"/>
                <Setter Property="FontSize" Value="14"/>
            </Style>
            <Style x:Key="numberStyle" TargetType="TextBlock" BasedOn="{StaticResource baseStyle}">
                <Setter Property="FontSize" Value="40"/>
                <Setter Property="Foreground" Value="DarkGray"/>
                <Setter Property="Background" Value="LightGray"/>
            </Style>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="10"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="70"/>
            <RowDefinition Height="70"/>
            <RowDefinition Height="70"/>
            <RowDefinition Height="10"/>
        </Grid.RowDefinitions>
        <TextBlock Text="Cartón Nº:" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Right" Style="{StaticResource baseStyle}"/>
        <TextBlock x:Name="CardNumber" Text="{Binding CardNumber}" Grid.Column="2" Grid.Row="0" Style="{StaticResource baseStyle}"/>
        <TextBlock Text="Serie:" Grid.Column="4" Grid.Row="0" HorizontalAlignment="Right" Style="{StaticResource baseStyle}"/>
        <TextBlock x:Name="SerialNumber" Text="{Binding SerialNumber}" Grid.Column="5" Grid.Row="0" Style="{StaticResource baseStyle}"/>

        <TextBlock x:Name="Row11" Text="{Binding cardNumbers[0]}" Grid.Column="1" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row12" Text="{Binding cardNumbers[1]}" Grid.Column="2" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row13" Text="{Binding cardNumbers[2]}" Grid.Column="3" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row14" Text="{Binding cardNumbers[3]}" Grid.Column="4" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row15" Text="{Binding cardNumbers[4]}" Grid.Column="5" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row16" Text="{Binding cardNumbers[5]}" Grid.Column="6" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row17" Text="{Binding cardNumbers[6]}" Grid.Column="7" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row18" Text="{Binding cardNumbers[7]}" Grid.Column="8" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row19" Text="{Binding cardNumbers[8]}" Grid.Column="9" Grid.Row="1" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row21" Text="{Binding cardNumbers[9]}" Grid.Column="1" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row22" Text="{Binding cardNumbers[10]}" Grid.Column="2" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row23" Text="{Binding cardNumbers[11]}" Grid.Column="3" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row24" Text="{Binding cardNumbers[12]}" Grid.Column="4" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row25" Text="{Binding cardNumbers[13]}" Grid.Column="5" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row26" Text="{Binding cardNumbers[14]}" Grid.Column="6" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row27" Text="{Binding cardNumbers[15]}" Grid.Column="7" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row28" Text="{Binding cardNumbers[16]}" Grid.Column="8" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row29" Text="{Binding cardNumbers[17]}" Grid.Column="9" Grid.Row="2" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row31" Text="{Binding cardNumbers[18]}" Grid.Column="1" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row32" Text="{Binding cardNumbers[19]}" Grid.Column="2" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row33" Text="{Binding cardNumbers[20]}" Grid.Column="3" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row34" Text="{Binding cardNumbers[21]}" Grid.Column="4" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row35" Text="{Binding cardNumbers[22]}" Grid.Column="5" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row36" Text="{Binding cardNumbers[23]}" Grid.Column="6" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row37" Text="{Binding cardNumbers[24]}" Grid.Column="7" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row38" Text="{Binding cardNumbers[25]}" Grid.Column="8" Grid.Row="3" Style="{StaticResource numberStyle}"/>
        <TextBlock x:Name="Row39" Text="{Binding cardNumbers[26]}" Grid.Column="9" Grid.Row="3" Style="{StaticResource numberStyle}"/>
    </Grid>
</UserControl>

El código XAML del formulario principal

   
<Window x:Class="Bingo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Bingo"
        mc:Ignorable="d"
        Title="MainWindow" Height="401.567" Width="812.461">
    <Grid>
        <local:CardForm x:Name="cardform"/>
        <Button x:Name="button" Content="Generar" HorizontalAlignment="Left" Height="38" Margin="591,306,0,0" VerticalAlignment="Top" Width="136" Click="button_Click"/>
    </Grid>
</Window>

en este bloque de código, debemos fijarnos en como estamos declarando el control de usuario que hemos creado y que ahora estamos insertando en el formulario principal; primero declaramos el espacio clr-namespace:Bingo con la key local (línea 6) y luego llamamos al control desde esta key que hace referencia al espacio de nombres donde se encuentra el control, si no hacemos esto, jamás podremos declarar el control de usuario dentro de la ventana principal, porque no lo encontraría.

y el code behind de este form, solo esto, el resto lo hace WPF. En este bloque cabe destacar que los único que hacemos desde la ventana principal es instanciar un objeto Card (Cartón) y asignar al DataContext del grid del control de usuario, el objeto instanciado card. El botón, lo único que hace es llamar al método CartonNuevo mostrando cada vez que se pulse, un nuevo cartón aleatorio.

namespace Bingo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        void CartonNuevo()
        {
                Card card = new Card();
                card.CardNumber = 1729;
                card.SerialNumber = 1;
                cardform.CardGrid.DataContext = card;
        }

        private void button_Click(object sender, RoutedEventArgs e)
        {
            CartonNuevo();            
        }
    }
}

Volvemos al código XAML del control de usuario, en el control grid hay varias cosas que ver

  • En el bloque Resources del Grid, creo un Style para los Textblock llamado baseStyle
  • Luego creo otro estilo llamado numberStyle que hereda de baseStyle y modifica algunas propiedades (Está subrayado desde la línea 9 a la 22)
  • En las propiedades Text de los controles Textblock, hacemos un Binding al elemento concreto de la colección List (Text="{Binding cardNumbers[0]}") del objeto Card, al número de cartón y a la serie, de modo que sin ningún código en C#, el formulario nos mostrará el cartón, solo debemos decirle desde el formulario principal, que el DataContext es el objeto instanciado card. (Dejo subrayado como ejemplo el primer TextBlock de la línea 48)

He aquí como quedaría el resultado

bingo03

como siempre, si tenéis alguna duda o pregunta, comentario al canto

Saludos

Sistema de numeración en base primorial

Sistema de numeración en base primorial

En mi otro blog, hace poco estuve hablando de un sistema de posicionamiento mixto como era el sistema de numeración en base primorial, el cual varía en cada posición la base, bueno pues he creado una estructura en la cual se incluye además de la conversión, los operadores de suma, resta, multiplicación y división y los de comparación. Es una estructura sencilla en la que no me he esmerado mucho, pero espero que quede claro el uso de estructuras, interfaces y sobrescritura de operadores. Lo primero de todo, ¿por que una estructura y no una clase?. Antes de nada vamos a enumerar las características de las estructuras:

  • Una estructura hereda de la clase abstracta System.ValueType y esta a su vez de object
  • Una estructura no permite la herencia, solo pueden implementar interfaces
  • Una estructura no puede ser abstracta
  • No permite un constructor sin parámetros y no puede tener destructor.
  • Si contiene campos o propiedades, tienen que ser asignados en el constructor
  • Las variables no pueden ser inicializadas fuera del constructor
  • Es posible crear una estructura sin usar el operador new, en tal caso, no es posible usar la estructura hasta que todos los campos se inicialicen.

¿Para que se utilizan? Para representar objetos de poca monta que no tengan grandes cálculos (también podríamos hacerlo con una clase), pero lo primero, nos aseguramos que se han inicializado todas las variables y campos y en el caso de tener un constructor, contiene un parámetro de entrada como mínimo, además no es necesario instanciar la estructura mediante new. El típico ejemplo de una estructura sería un sistema de coordenadas, como podemos ver en el ejemplo, se asignan los valores en el constructor y podemos tener

    public struct Cooordenadas3D
    {
        public int x, y, z;

        public Cooordenadas3D(int _x, int _y, int _z)
        {
            x = _x;
            y = _y;
            z = _z;
        }

        public override string ToString()
        {
            return "(" + x + ", " + y + ", " + z + ")";
        }

        public double GetModulo()
        {
            //.... return ...
        }
        public double GetAngulo()
        {
            //.... return ...
        }

Y yo con mi sistema de numeración en base primorial lo he hecho, he implementado el sistema mediante una estructura y que vamos a detallar con detenimiento.

Lo primero, hemos dicho que no puede heredar de ninguna clase, pero si puede implementar interfaces. Yo implemento las interfaces IComparable, IComparable<PrimorialBaseNumber>, IEquatable<PrimorialBaseNumber>. La primera y la segunda implementan el método CompareTo que nos hará falta para los operadores y la tercera, que implementa el método Equals que nos hará falta también para los operadores.

He acotado el código en regiones y dentro de estas, explico que hay:

  • Constructor (contructor). En el constructor, le pasamos como parámetro el número decimal que queremos convertir a nuestro sistema y dentro de este inicializamos el array de los primero 45 números primos y ¿por qué 45? esta lista se usará para generar el primorial y el primorial 45 o lo que es lo mismo 199# equivale a 7.7999220416834615532491991063298e+81 que para este ejemplo creo que va bien, al mismo tiempo, este número tan grande, lo he usado como valor máximo del sistema, (por acotar)
  • Constants (constantes). He declarado cinco constantes, de la cuales realmente solo usamos dos, el valor máximo y mínimo (el resto las tengo de otras estructuras y clases y las he dejado). como he comentado, el valor máximo y mínimo, es el valor del primorial de 199 (199#)
  • Properties and Fields (Campos y propiedades). Un array estática con los 45 primeros números primos, un propiedad para almacenar el valor en decimal (DecimalValue) y otra el valor en base primorial (Value). Dentro de la propiedad DecimalValue, se calcula el valor de Value, de modo que si cambiamos el valor del primero, se calcula el del segundo.
  • Public Methods (Métodos públicos).
    • Comparisons (comparaciones). En esta región entran los métodos CompareTo de objeto y del tipo PrimorialBaseNumber, obligatoriamente implementados ya que la interface IComparable, IComparable así lo marcan. Estos devuelven 1 si es mayor, 0 si es igual y -1 si es menor y el método Equals por la interfaz IEquatable<PrimorialBaseNumber>; esta devuelve true si los dos objetos son iguales.
    • Conversions (conversiones). En esta región, tres métodos y uno de ellos sobrecargado. ToString() que devuelve el valor en base primorial, ToDecimal() que devuelve el valor en base decimal y ToPrimorialBase()ToPrimorialBase(decimal _number), el primero hace lo mismo que ToString() pero devuelve un valor tipo decimal y el segundo asigna el valor en base decimal a DecimalValue y en consecuencia lo convierte y devuelve el número convertido.
    • Operators (operadores). Un ejemplo de sobrecarga de los operadores lo expuse en el post de calculadora de números complejos, en el que si yo sumo el objeto complex con otro complex, el compilador no sabe como ejecutar el cálculo, mientras que si sobrecargamos el método, podemos decirle como efectuar esa operación y cada vez que se use, así lo hará. He sobrecargado todos los operadores posibles, para hacerlo lo más genérico posible. Para realizar las operaciones podría haber usado un método más elemental matemáticamente hablando, sumando dígito a dígito, acarreos, etc., pero lo he hecho con las operaciones en base decimal, es más fácil, ¿no?.
    • Static (Estáticos). En esta región incluyo la obtención del primorial mediante un método estático, el cual puede ser accesible desde fuera y dentro de la estructura.
  • Private methods (métodos privados). aquí iría el motor de la estructura, que para el usuario debe estar oculto (tu no ves en tu coche los pistones moverse o las explosiones en los cilindros, ¿verdad?), donde se calculan las conversiones a base decimal, a base primorial.

y ahora el código

/* ************************************************************************************
* © JOAQUIN MARTINEZ RUS 2016
* PROYECTO:         PrimorialBaseNumber
* Nombre:           Mersenne
* Archivo:          PrimorialBaseNumber.cs
* Descripción:      Estructura de un sistema de numeración mixto primorial
* Historial:
*                   1. Joaquin Martínez Rus - 19 ago 2016. Creación
*
* Comentarios:      Contempla la conversión de números en base decimal a base primorial y viceversa,
*                   así como los operadores de suma, resta, multiplicación, división, resto, igualdad,
*                   distinto, complemento y comparativos
*
*
************************************************************************************/
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System
{
    public struct PrimorialBaseNumber : IComparable, IComparable, IEquatable
    {
        #region Constructor

        public PrimorialBaseNumber(decimal _DecimalNumber)
        {
            // Inicializando todas las propiedades y variables
            PrimorialBaseNumber.primeNumber = new decimal[] {
                2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
                31, 37, 43, 47, 53, 59, 61, 67, 71, 73,
                79, 83, 89, 97, 101, 103, 107, 109, 113, 127,
                131, 137, 139, 149, 151, 157, 163, 167, 173, 179,
                181, 191, 193, 197, 199 };
            Value = 0;
            _decimalValue = 0;
            DecimalValue = _DecimalNumber;

        }

        #endregion

        #region Constants

        public const double MaxValue = 7.7999220416834615532491991063298e+81; // 199#
        public const double MinValue = -7.7999220416834615532491991063298e+81;
        public const double NaN = 0D / 0D;
        public const double NegativeInfinity = -1D / 0D;
        public const double PositiveInfinity = 1D / 0D;

        #endregion

        #region Properties and Fields

        private decimal _decimalValue;

        public decimal DecimalValue
        {
            get { return _decimalValue; }
            set
            {
                _decimalValue = value;
                Value= ConvertToPrimorialBaseNumber(_decimalValue);
            }
        }

        public decimal Value { get; set; }

        public static decimal[] primeNumber { get; set; }

        #endregion

        #region Public Methods

        #region Comparisons

        /// <summary>
        /// Compara esta instancia con un objeto específico y retorna un entero que indica
        /// si el valor es menor que, igual a o mayor que el valor de objeto específico
        /// </summary>

        /// Objeto a comparar a null
        /// Retorna un entero con signo. Negativo si es menor, un 0 si es igual y un entero positivo si es mayor.
        /// Si el valor es null, retorna error
        public int CompareTo(object obj)
        {
            try
            {
                if (obj == null) throw new ArgumentNullException();

                return CompareTo((PrimorialBaseNumber)obj);
            }
            catch (Exception)
            {
                throw new FormatException("Error en la conversión del tipo Object al tipo PrimorialBaseNumber");
            }
        }

        /// <summary>
        /// Compara esta instancia con un objeto específico y retorna un entero que indica
        /// si el valor es menor que, igual a o mayor que el valor de objeto específico
        /// </summary>

        /// Objeto a comparar a null
        /// Retorna un entero con signo. Negativo si es menor, un 0 si es igual y un entero positivo si es mayor.
        /// Si el valor es null, se devuelve NaN
        public int CompareTo(PrimorialBaseNumber obj)
        {
            int value = 0;

            if (DecimalValue &gt; obj.DecimalValue)
            {
                value = 1;
            }
            else if (DecimalValue &lt; obj.DecimalValue)
            {
                value = -1;
            }

            return value;
        }

        /// <summary>
        /// Retorna un valor que indica si esta instancia es igual a un PrimorialBaseNumber específico
        /// </summary>

        /// Un objeto PrimorialBaseNumber a comparar con esta instancia
        /// True si tiene el mismo valor que esta instancia
        public bool Equals(PrimorialBaseNumber other)
        {
            try
            {
                return this == other;
            }
            catch (Exception)
            {

                throw new FormatException();
            }

        }

        /// <summary>
        /// Retorna un valor que indica que esta instancia es igual a un objeto específico
        /// </summary>

        /// Un objeto a comparar con esta instancia
        /// True si es una instancia del tipo PrimorialBaseNumber y tiene el mismo valor que esta instancia
        public override bool Equals(Object other)
        {
            try
            {
                return Equals((PrimorialBaseNumber)other);
            }
            catch (Exception)
            {
                throw new FormatException("Error en la conversión del tipo Object al tipo PrimorialBaseNumber");
            }
        }

        #endregion

        #region Conversions

        /// <summary>
        /// Convierte el valor numérico de esta instancia a su equivalente string
        /// </summary>

        /// El equivalente a string de esta instancia
        public override string ToString()
        {
            return Value.ToString();
        }

        /// <summary>
        /// Obtiene el valor numérico en base decimal
        /// </summary>

        /// El equivalente al número en base decimal
        public decimal ToDecimal()
        {
            return DecimalValue;
        }

        /// <summary>
        /// Obtiene el valor numérico en base primorial
        /// </summary>

        /// El equivalente al número en base primorial
        public decimal ToPrimorialBase()
        {
            return Value;
        }

        /// <summary>
        /// Convierte el número del parámetro en base decimal a base primorial y
        /// retorna el número convertido asignándolo al valor de esta instancia
        /// </summary>

        /// Número en base decimal que se va a convertir a base primorial
        /// Número en base primorial
        public decimal ToPrimorialBase(decimal _number)
        {
            DecimalValue = _number;
            return Value;
        }

        #endregion        

        #region Operators

        /// <summary>
        /// Retorna un valor que indica la suma de ambos términos
        /// </summary>

        /// Primer valor de la suma
        /// Segundo valor de la suma
        /// Valor de la suma de ambos términos en base Primorial
        public static PrimorialBaseNumber operator + (PrimorialBaseNumber left, PrimorialBaseNumber right)
        {
            decimal value = left.DecimalValue + right.DecimalValue;

            if ((double)value &gt; MaxValue || (double)value &lt; MinValue)
            {
                throw new OverflowException(&quot;El valor de la suma es superior o inferior al valor máximo o mínimo respectivamente de la instancia&quot;);
            }
            return new PrimorialBaseNumber(value);
        }

        /// <summary>
        /// Retorna un valor que indica la resta de ambos términos
        /// </summary>

        /// Primer valor de la resta
        /// Segundo valor de la resta
        /// Valor de la resta de ambos términos en base Primorial
        public static PrimorialBaseNumber operator -(PrimorialBaseNumber left, PrimorialBaseNumber right)
        {
            decimal value = left.ConvertToDecimal() - right.ConvertToDecimal();

            if ((double)value &gt; MaxValue || (double)value &lt; MinValue)
            {
                throw new OverflowException(&quot;El valor de la resta es superior o inferior al valor máximo o mínimo respectivamente de la instancia&quot;);
            }
            return new PrimorialBaseNumber(value);
        }

        /// <summary>
        /// Retorna un valor que indica el producto de ambos términos
        /// </summary>

        /// Primer valor del producto
        /// Segundo valor del producto
        /// Valor del producto de ambos términos en base Primorial
        public static PrimorialBaseNumber operator * (PrimorialBaseNumber left, PrimorialBaseNumber right)
        {
            decimal value = left.ConvertToDecimal() * right.ConvertToDecimal();

            if ((double)value &gt; MaxValue || (double)value &lt; MinValue)
            {
                throw new OverflowException(&quot;El valor del producto es superior o inferior al valor máximo o mínimo respectivamente de la instancia&quot;);
            }
            return new PrimorialBaseNumber(value);
        }

        /// <summary>
        /// Retorna un valor que indica la división de ambos términos
        /// </summary>

        /// Primer valor de la división
        /// Segundo valor de la división
        /// Valor de la división de ambos términos en base Primorial
        public static PrimorialBaseNumber operator / (PrimorialBaseNumber left, PrimorialBaseNumber right)
        {
            if (right.ConvertToDecimal()==0)
            {
                throw new DivideByZeroException();
            }

            decimal value = left.ConvertToDecimal() / right.ConvertToDecimal();

            if ((double)value &gt; MaxValue || (double)value &lt; MinValue)
            {
                throw new OverflowException(&quot;El valor de la división es superior o inferior al valor máximo o mínimo respectivamente de la instancia&quot;);
            }
            return new PrimorialBaseNumber(value);
        }

        /// <summary>
        /// Retorna un valor que indica el resto de la división de ambos términos
        /// </summary>

        /// Primer valor de la operación resto
        /// Segundo valor de la operación resto
        /// Valor del resto de la división de ambos términos en base Primorial
        public static PrimorialBaseNumber operator % (PrimorialBaseNumber left, PrimorialBaseNumber right)
        {
            decimal value = left.DecimalValue % right.DecimalValue;
            return new PrimorialBaseNumber(value);
        }

        /// <summary>
        /// Retorna un valor que indica el complemento de esta instancia
        /// </summary>

        /// Valor del número al que se le calcula el complemento
        /// Valor del complemento del número
        public static PrimorialBaseNumber operator !(PrimorialBaseNumber _number)
        {
            int index = 0;
            decimal primorial = 1;
            decimal complemento = 0;
            // Buscar el primer primorial que sea mayor que el número
            do
            {
                primorial = GetPrimorial(index);
                index++;
            } while (primorial &lt; _number.DecimalValue);

            complemento = primorial - _number.DecimalValue;

            return new PrimorialBaseNumber(complemento);
        }

        /// <summary>
        /// Retorna un valor que indica que ambos valores son iguales
        /// </summary>

        /// Primer valor de la comparación
        /// Segundo valor de la comparación&lt;
        /// Verdadero si el valor de la izquierda y el de la derecha son iguales; en otro caso, retorna false
        public static bool operator ==(PrimorialBaseNumber left, PrimorialBaseNumber right)
        {
            bool isEqual = true;

            if (left.ToString().Length!= right.ToString().Length)
            {
                isEqual = false;
            }
            for (int i = 0; i &lt; left.ToString().Length; i++)
            {
                if (left.ToString()[i]!=right.ToString()[i])
                {
                    isEqual = false;
                    break;
                }
            }

            return isEqual;
        }

        /// <summary>
        /// Retorna un valor que indica que ambos valores no son iguales
        /// </summary>

        /// Primer valor de la comparación
        /// Segundo valor de la comparación
        /// Verdadero si el valor de la izquierda y el de la derecha no son iguales; en otro caso, retorna false
        public static bool operator !=(PrimorialBaseNumber left, PrimorialBaseNumber right)
        {
            return !(left==right);
        }

        /// <summary>
        /// Retorna un valor que indica que un valor específico es menor que otro
        /// </summary>

        /// Primer valor de la comparación
        /// Segundo valor de la comparación
        /// Verdadero si el valor de la izquierda es menor que el de la derecha; en otro caso, retorna false
        public static bool operator &lt;(PrimorialBaseNumber left, PrimorialBaseNumber right)
        {
            if (left.CompareTo(right) ==-1)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Retorna un valor que indica que un valor específico es mayor que otro
        /// </summary>

        /// Primer valor de la comparación
        /// Segundo valor de la comparación
        /// Verdadero si el valor de la izquierda es mayor que el de la derecha; en otro caso, retorna false
        public static bool operator &gt;(PrimorialBaseNumber left, PrimorialBaseNumber right)
        {
            if (left.CompareTo(right) == 1)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Retorna un valor que indica que un valor específico es menor o igual que otro
        /// </summary>

        /// Primer valor de la comparación
        /// Segundo valor de la comparación
        /// Verdadero si el valor de la izquierda es menor o igual que el de la derecha; en otro caso, retorna false
        public static bool operator &lt;=(PrimorialBaseNumber left, PrimorialBaseNumber right)
        {
            if (left.CompareTo(right) == -1 || left.CompareTo(right)==0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Retorna un valor que indica que un valor específico es mayor o igual que otro
        /// </summary>

        /// Primer valor de la comparación
        /// Segundo valor de la comparación
        /// Verdadero si el valor de la izquierda es mayor o igual que el de la derecha; en otro caso, retorna false
        public static bool operator &gt;=(PrimorialBaseNumber left, PrimorialBaseNumber right)
        {
            if (left.CompareTo(right) == 1 || left.CompareTo(right) == 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region Static

        /// <summary>
        /// Retorna el valor del primorial de n
        /// </summary>

        /// Valor de n en el conjunto de los números primos
        /// Retorna el valor correspondiente al primorial de n
        public static decimal GetPrimorial(int n)
        {

            if (n &gt; primeNumber.Length)
            {
                throw new OverflowException();
            }

            decimal result = 1;

            for (int i = 0; i &lt; n; i++)
            {
                Decimal prime = primeNumber[i];
                result *= prime;
            }
            return result;
        }

        #endregion

        #endregion

        #region Private Methods

        /// <summary>
        /// Convierte un número en base decimal a base primorial
        /// </summary>

        /// Número que se va a convertir
        /// Valor decimal del número en base primorial
        private decimal ConvertToDecimal(decimal _number)
        {
            IEnumerable arrayToString = _number.ToString().ToCharArray().Reverse();
            Decimal result = 0;
            int contador = 0;
            foreach (var item in arrayToString)
            {
                Decimal primorial = GetPrimorial(contador++);
                int digit = Convert.ToInt32(item.ToString());
                result += digit * primorial;

            }
            return result;
        }

        /// <summary>
        /// Convierte el valor en base decimal de esta instancia a base primorial
        /// y lo asigna al valor en base primorial de esta instancia
        /// </summary>

        /// Valor decimal del número en base primorial
        private decimal ConvertToDecimal()
        {
            return ConvertToDecimal(Value);
        }

        /// <summary>
        /// Convierte un número decimal en base 10 a base primorial
        /// </summary>

        /// Número a convertir
        /// Número en base primorial
        private decimal ConvertToPrimorialBaseNumber(decimal _number)
        {
            StringBuilder result = new StringBuilder();
            decimal dividendo = _number;
            decimal resto = 0;

            int index = 0;

            do
            {
                resto = dividendo % primeNumber[index];
                dividendo = Math.Truncate(dividendo / primeNumber[index]);
                result.Insert(0, resto);
                index++;
            } while (dividendo &gt; 0);

            return Convert.ToDecimal(result.ToString());
        }

        #endregion

    }
}

y para ver como funciona, o creáis un formulario y le añadis la estructura o creáis una aplicación de consola y hacéis lo mismo. Yo he creado un formulario, le he añadido un textbox y le he ido añadiendo los calculos para ver que funciona

            // Iniciando estructuras
            PrimorialBaseNumber pbnn1 = new PrimorialBaseNumber(907);
            PrimorialBaseNumber pbnn2 = new PrimorialBaseNumber(487);            

            textBox.AppendText("487=> " + pbnn1.ToPrimorialBase(487) + "\n");
            textBox.AppendText("907=> " + pbnn1.ToPrimorialBase(907) + "\n");

            textBox.AppendText("suma 487+907 => " + (pbnn1 + pbnn2) + "\n");
            textBox.AppendText("resta 907-487 => " + (pbnn1 - pbnn2) + "\n");
            textBox.AppendText("multi 487*907 =>" + (pbnn1 * pbnn2) + "\n");
            textBox.AppendText("!1544=> " + (2310-1544) + "\n");
            pbnn1.ToPrimorialBase(1544);
            textBox.AppendText("!1544=> " + !pbnn1 + "\n");
            textBox.AppendText("766=> " + !pbnn1 + "\n");

y este el resultado

487=> 22101
907=> 42101
suma 487+907 => 64210
resta 907-487 => 20000
multi 487*907 => 14922301
!1544=> 766
!1544=> 34220
766=> 34220

como siempre, comentad el código y mantenedlo organizado y si tenéis alguna pregunta, no dudéis en dejar un comentario

saludos

Pd: Repito, esta estructura está realizada en una tarde, por lo que entiendo que es muy mejorable y debe abarcar todos las cotas y aspectos, pero para aprender , a mi parecer, está bien, además como mejor se aprende, es picando código!

14.- Instrucciones de Salto

14.- Instrucciones de Salto
Hola a todos de nuevo. Hoy vamos a estudiar las instrucciones de salto. ¿Y que son las instrucciones de salto? Todos sabemos que en un programa las instrucciones se ejecutan secuencialmente, pues estas, nos permiten saltar de una parte del programa a otro evitando esa ejecución secuencial.Las instrucciones de salto para C# son:

  • break
  • continue
  • goto
  • return
  • throw

Y para VB.NET

  • Exit For
  • Exit Do
  • Exit While
  • Exit Select
  • Continue For
  • goto
  • Return

Paso a paso, vamos a detallar una por una excepto la última que la incluiremos en el tratamiento de errores.

La instrucción break nos permita terminar un bucle o salir de una instrucción switch, es decir por ejemplo, si creamos un bucle do while que vaya sumando 1 a una variable y le ponemos la condición de que cuando esa variable llegue a 100 salga del bucle, pues en la parte verdadera de esa condición es donde debemos poner el break, para que así salga y no siga sumando. Para VB.NET, se utiliza Exit For para un bucle For Next, Exit Do para un bucle Do While, Exit While para un bucle While y Exit Select para una instrucción Select Case. Como veis, C# se complica menos y utiliza break para todos los casos.

for (int i = 1; i <= 1000; i++)
{
	if (i == 100)
	{
		break;
	}
	Console.WriteLine(i);
}

Mismo caso pero con una instrucción while

int n = 0;
while (true)
{
	n += 1;
	if (n==100)
	{
		break;
	}
}

Y su equivalente para Vb.NET

        For i As Integer = 1 To 1000
            If i = 100 Then
                Exit For
            End If
            Console.WriteLine(i)
        Next
        Dim n As Integer = 0
        While True
            n += 1
            If n = 100 Then
                Exit While
            End If
        End While

continue, es una instrucción que no suelo utilizar a menudo, pero es curiosa en cuanto a su funcionamiento. Esta lo que provoca dentro de un bucle for o foreach, pasar a la siguiente iteración, por ejemplo.

            for (int i = 1; i <= 10; i++)
            {
                if (i < 9)
                {
                    continue;
                }
                Console.WriteLine(i);
            }

En este ejemplo cuando i es menor que 9 la instrucción continue evita que se escriba en la consola pasando a darle un valor nuevo a i hasta que i es mayor o igual a 9, donde entonces se imprimirá el valor en pantalla. Su equivalente en VB.NET es Continue For.

        For i As Integer = 1 To 10
            If i < 9 Then
                Continue For
            End If
            Console.WriteLine(i)
        Next

Goto transfiera o envía el control del programa a un sitio concreto del programa y ¿cómo sabe donde tiene que enviarlo?, pues porque en ese lugar debe existir una etiqueta. También puede usarse para transferir el control de programa a una etiqueta de una instrucción switch. Además, como es normal, podemos usarla también para salir de bucles, ¿no crees? Exponemos ejemplos. En el primero, aparecerá en pantalla ‘He salido’ cuando el valor de i valga 9.

C#

            for (int i = 1; i <= 10; i++)
            {
                if (i > 8)
                {
                    goto SalidaPrograma;
                }
            }
        SalidaPrograma:
            Console.WriteLine("He salido");

VB.NET


        For i As Integer = 1 To 10
            If i > 8 Then
                GoTo SalidaPrograma
            End If
        Next
SalidaPrograma:
        Console.WriteLine("He salido")

En el caso de salto dentro de una instrucción switch, lo vemos a continuación en el que dependiendo del valor de n, (el resto no me valen), en el que para el caso de 2 y 3, efectuará la suma propia del caso, pero además lo reenviará al valor 1 donde volverá a sumarle 25, ¿ok?
C#

            int valor=0;
            switch (n)
            {
                case 1:
                    valor += 25;
                    break;
                case 2:
                    valor += 25;
                    goto case 1;
                case 3:
                    valor += 50;
                    goto case 1;
                default:
                    Console.WriteLine("Esto no me vale");
                    break;
            }

VB.NET


        Dim valor As Integer = 0
        Select Case n
            Case 1
                valor += 25
                Exit Select
            Case 2
                valor += 25
             goto case 1
            Case 3
                valor += 50
             goto case 1
            Case Else
                Console.WriteLine("Esto no me vale")
                Exit Select
        End Select

La instrucción return acaba la ejecución del método donde se ha ejecutado y devuelve el control al método que llamó antes, además puede devolver algún valor. Con un ejemplo lo veremos con más claridad.

Si ejecutamos el método Inicio, se llamará al método CalcularAreaCirculo con un valor de radio igual a 5. Dentro del método de cálculo, se efectuarán los cálculos pertinentes para obtener el área y acto seguido se ejecuta return area, devolviendo el valor del área al método que lo llamó que es Inicio. Una vez este tiene el valor, lo visualiza en pantalla. Lo que si tenemos que tener en cuenta para VB.NET, es que para que un método devuelva un valor, debe ser una función (Function), cosa que en C# en vez de void se le asigna un tipo.

C#

            private void Inicio()
            {
                int radius = 5;
                double result = CalcularAreaCirculo(radius);
                Console.WriteLine("El area es {0:0.00}", result);
                Console.ReadKey();
            } 

            private double CalcularAreaCirculo(int r)
            {
                double area = r * r * Math.PI;
                return area;
            }

VB.NET

    Private Sub Inicio()
        Dim radius As Integer = 5
        Dim result As Double = CalcularAreaCirculo(radius)
        Console.WriteLine("El area es {0:0.00}", result)
        Console.ReadKey()
    End Sub

    Private Function CalcularAreaCirculo(r As Integer) As Double
        Dim area As Double = r * r * Math.PI
        Return area
    End Function

Pues esto es todo en cuanto a las instrucciones de salto, fácil, ¿no?. Pues nos vemos en la próxima entrega!!

Saludos “der Waki”

13.- Instrucciones de iteración

13.- Instrucciones de iteración

Por fin he vuelto. Estimados amigos, he tenido que dejar por una temporada el blog de Aprender a programar para mejorar mis conocimientos anglo-lingüísticos y me han absorbido todo el tiempo del mundo, pero a partir de ahora… si nada ni nadie lo impide, seguiremos en primera línea. Vamos allá.Bueno pues las instrucciones de iteración nos permiten repetir ciertas acciones o crear un bucle. Un bucle se puede repetir un número de veces limitado porque aunque se puede hacer ilimitado no tiene ningún sentido crear un programa que no acabe nunca, ¿no?. Dentro de estos bucles se ejecutan las instrucciones ordenadamente salvo cuando utilizamos otro tipo de instrucciones que son las de salto.

Tenemos varios tipos de instrucciones de salto y la primera de todas es do-while (hacer mientras…). Ahora vamos a ver un ejemplo para C# y su equivalente para VB .NET. Esta instrucción ejecuta un bloque de instrucciones repetidas veces hasta que una determinada instrucción se evalúa como false. Para C# el bloque de código se incluye entre llaves y acto seguido se incluye la expresión booleana. En el siguiente ejemplo creamos una pequeña aplicación que imprimirá por pantalla los valores desde 0 a 2. Como veis, al entrar en el bucle, se imprime el primer valor de x que es cero, después le suma a x una unidad (x++ esto de verdad que suma una unidad, es un operador y lo veremos en un futuro) , efectúa la comprobación x vale ahora 1, ¿1 es menor que 3?, si (true), pues volvemos al inicio del bucle do y se vuelve a ejecutar el mismo código hasta que x llega a tener el valor de 3 que en ese caso 3 no es menor que 3 y la expresión booleana sería false. En este momento, saldría del bucle. Para VB.NET es igual pero con Do y Loop While donde el contenido de código incluido entre ambas instrucciones, es lo que se ejecutará mientras la expresión While sea false. El aspecto fundamental de esta instrucción es que por lo menos, el bloque de código se ejecuta una sola vez ya que una vez ejecutado es cuando se comprueba la expresión booleana que permitirá seguir ejecutando este o salir del bucle.

C#

public class DoWhile_Prueba
    {
        public static void Main()
        {
            int x = 0;
            Console.WriteLine("SALIDA EN PANTALLA");
            do
            {
                Console.WriteLine(x);
                x++;
            } while (x &lt; 3);
            Console.WriteLine("FIN DE BUCLE");
        }
    }
    /* SALIDA EN PANTALLA:
      	0
        1
        2
    */

Vb.NET

Public Class DoWhile_Prueba
    Public Shared Sub Main()
        Dim x As Integer = 0
        Console.WriteLine("SALIDA EN PANTALLA")
        Do
            Console.WriteLine(x)
            x += 1
        Loop While x &lt; 3
        Console.WriteLine("FIN DE BUCLE")
    End Sub
End Class
'
'        SALIDA EN PANTALLA:
'        0
'        1
'        2

Ahora vamos a ver la instrucción while y su equivalente en VB.NET While y End While. En este tipo de instrucciones, la expresión booleana se verifica al principio, por tanto si al comienzo no se verifica la expresión , no se ejecutará ni una sola vez el contenido de código del bucle (esta es la principal diferencia con Do-While). Con esta instrucción, he incluido un cosita nueva, en la anterior instrucción metimos un operador de adición y en esta un parámetro. Cuando le damos formato a un texto como es en el siguiente caso, una vez vale una cosa y a la siguiente vale otra, bueno pues utilizamos unas llaves con un número en su interior y cuando se ejecute, se buscará por orden el primer parámetro disponible. Un ejemplo y lo entenderéis.

Console.WriteLine("Hola {0} {0}, me llamo {1} {2}, y esto es una {3}", "amigos", "Joaquín", "Martínez", "prueba");

{0} equivale a amigos, {1} a Joaquín, {2} a Martínez y {3} a prueba.
Esto imprimirá en pantalla “Hola amigos amigos, me llamo Joaquín Martínez y esto es una prueba”. Si os dais cuenta en cada hueco donde antes existía una llave y un número, ahora existe su equivalente en texto con el mismo orden en el que encontraba. Aunque no lo parezca, esto es muy útil!

C#

class WhileTest_Prueba
    {
        static void Main()
        {
     Console.WriteLine("Salida en pantalla")
            int n = 1;
            while (n < 6)
            {
                Console.WriteLine("Valor actual de n es {0}", n);
                n++;
            }
        }
    }
    /* Salida en pantalla:
        Valor actual de n es 1
        Valor actual de n es 2
        Valor actual de n es 3
        Valor actual de n es 4
        Valor actual de n es 5
     */

VB.NET

Class WhileTest_Prueba
    Private Shared Sub Main()
        Dim n As Integer = 1
        Console.WriteLine("Salida en pantalla")
        While n < 6
            Console.WriteLine("Valor actual de n es {0}", n)
            n += 1
        End While
    End Sub
End Class
'
'        Salida en pantalla:
'        Valor actual de n es 1
'        Valor actual de n es 2
'        Valor actual de n es 3
'        Valor actual de n es 4
'        Valor actual de n es 5

Ojo con este tipo de instrucciones porque SIEMPRE deben tener una salida, es decir que alguna vez se debe cumplir el valor de la expresión en false para poder salir del bucle, porque si no se convertiría en un bucle infinito sin ninguna utilidad. En el siguiente ejemplo ocurriría esto. Ya que el valor de nes 4 y la instrucción se ejecutará mientras que n sea mayor que 3, por tanto entraría en un bucle sin fin. Otra forma de salir de un bucle es mediante break, goto o return para C# o Exit While o Exit For, GoTo o Return.

C#

class WhileTest_Prueba
    {
        static void Main()
        {
            int n = 4;
            while (n > 3)
            {
                Console.WriteLine("Valor actual de n is {0}", n);
                n++;
            }
        }
    }

VB.NET

Class WhileTest_Prueba
    Private Shared Sub Main()
        Dim n As Integer = 1
        Console.WriteLine("Salida en pantalla")
        While n < 6
            Console.WriteLine("Valor actual de n is {0}", n)
            n += 1
            Return
        End While
    End Sub
End Class
'
'        Output:
'        Valor actual de n es 1
'        Valor actual de n es 2
'        Valor actual de n es 3
'        Valor actual de n es 4
'        Valor actual de n es 5

Otra instrucción de iteración, for para C# y For Next para VB.NET. Estas se ejecutan n-veces hasta que una expresión sea false y son muy útiles para recorrer matrices o para casos en los que conocemos el número de veces que se va a repetir un bucle. Por ejemplo vamos a ejecutar una instrucción 5 veces. La instrucción for realiza varias funciones. El primero y solo se hace una vez es declarar un variable i asignándole el valor de 1 (podríamos asignarle el valor 34 o -23), después la expresión de evaluación en este caso i<=5, es decirse ejecutará hasta que el valor de i sea menor o igual que 5 y por último el valor siguiente después de ejecutar el bloque que en este caso incrementa en 1 el valor de i (podríamos restarle 1 a i mediante –i o sumarle 2 mediante i+=2 aunque para VB.NET se utiliza Step.
Nota: La diferencia entre ++i y i++ es que la suma se realiza antes o después de asignarle un valor. Os muestro un ejemplo:

int i=3;
int j= ++i;
Console.WriteLine("Valor actual de j es {0}", j)
// Valor actual de j es 4;
i=3;
j= i++;
Console.WriteLine("Valor actual de j es {0}", j)
// Valor actual de j es 3;

Para VB.NET cerramos el bloque de código mediante la instrucción Next.
C#

class For_Prueba
    {
        static void Main()
        {
            Console.WriteLine("Salida");
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine(i);
            }
        }
    }
    /* Salida:
    1
    2
    3
    4
    5
    */

Class For_Prueba
    Private Shared Sub Main()
        Console.WriteLine("Salida")
        For i As Integer = 1 To 5
            Console.WriteLine(i)
        Next
    End Sub
End Class
'
'    Salida:
'    1
'    2
'    3
'    4
'    5

Ejemplo de for obteniendo los números pares hasta 100
C#

for (int i = 1; i <= 100; i+=2)
{
       Console.WriteLine(i);
}

VB.NET

For i As Integer = 1 To 100 Step 2
       Console.WriteLine(i)
Next

Por último la instrucción foreach se utiliza para recorrer colecciones y ¿qué es una colección? Pues imaginemos que miramos la galería de fotos de nuestro móvil, seleccionamos una y vamos pasando una a una. Pues esto es lo que hace la instrucción foreach, pasar de una foto a otra por toda la colección.

¿Y qué hace? Pues el bloque de código contenido, se ejecutará para cada elemento de la colección. En el siguiente ejemplo, creamos una colección de números enteros y rotamos por cada uno de ellos. Como veis, esta instrucción tiene también varios elementos primero foreach, segundo y se ejecuta cada vez, crea un objeto del mismo tipo que los elementos de la colección y le asigna el valor de uno de estos y por último dice de donde debe extraer esos objetos. Pues para el siguiente caso se imprimirá por pantalla 0,1,1,2,3,5,8 y 13. Y a continuación expongo otra forma de recorrer una colección mediante una instrucción for diciendo que desde el valor con índice 0 hasta el valor menor que el número de elementos que tiene, imprima en pantalla el valor con el índice de la instrucción, curioso ¿no?

Para VB.NET foreach se convierte en For Each el valor del elemento declarado y la colección desde donde se extraerán los elementos acabando y cerrando el bloque de código con Next.
C#

int[] coleccion = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 };
foreach (int element in coleccion)
{
         System.Console.WriteLine(element);
}
for (int i = 0; i < coleccion.Length; i++)
{
         System.Console.WriteLine(coleccion[i]);
}
/* En ambos casos, se imprime:
    0
    1
    1
    2
    3
    5
    8
    13
    */

Dim coleccion As Integer() = New Integer() {0, 1, 1, 2, 3, 5, 8, 13}
For Each element As Integer In coleccion
          System.Console.WriteLine(element)
Next
For i As Integer = 0 To coleccion.Length - 1
          System.Console.WriteLine(coleccion(i))
Next

Pues esto esto esto es todo amigos!!

Saludos “der Waki”

 

12.- Instrucciones de Selección

12.- Instrucciones de Selección
Las instrucciones son acciones que realiza el programa. Existen acciones comunes como la declaración de variables, la asignación de valores, la llamada a métodos, el recorrido en bucle de una colección y la creación de bifurcaciones a uno u otro bloque de código en función de una condición. El orden en que se ejecutan las instrucciones en un programa se denomina flujo de control o flujo de ejecución.Para C#, una instrucción puede estar compuesta por una única línea de código que finaliza en un punto y coma o por una serie de instrucciones de una línea incluidas en un bloque. Como ya hemos visto, un bloque de instrucciones se encierra entre llaves {} y pueden contener bloques anidados. A continuación enumero las categorías:

  • Instrucciones de selección (if, else, switch, case)
  • Instrucciones de iteración (do, for, foreach, in, while)
  • Instrucciones de salto (break, continue, default, goto, return, yield)
  • Instrucciones de control de excepciones (throw, try-catch, try-finally, try-catch-finally)
  • Checked y unchecked (checked, unchecked)
  • Instrucción fixed (fixed)
  • Instrucción lock

En VB.NET

En Visual Basic, una instrucción es una instrucción completa.  Puede contener palabras clave, operadores, variables, constantes y expresiones y cada instrucción como en C#, pertenece a cada una de las categorías siguientes:

Instrucciones de declaración, que dan nombre a una variable, constante o procedimiento y también pueden especificar un tipo de datos.

Instrucciones ejecutables, que inician acciones.  Estas instrucciones pueden llamar a un método o función, y pueden repetirse en bloques de código o crear una bifurcación en otro bloque de código. Las instrucciones ejecutables incluyen instrucciones de asignación, que asignan un valor o expresión a una variable o constante.

En este capítulo, vamos a ver las instrucciones de selección para C# y las instrucciones ejecutables para VB.NET.

¿Empezamos por C#? Venga, pues una instrucción de selección, hace que el programa vaya a un determinado punto dependiendo de que cierta condición sea verdadero o falso (true o false). Las palabras if, else, switch, case y default son las palabras clave en las instrucciones de selección. Como son palabras clave, no pueden usarse como variables o nombres de métodos ya que el compilador nos daría un error.

La instrucción if identifica que sentencia debe ejecutarse dependiendo de un valor booleano (verdadero o falso, true o false), vemos un ejemplo:

bool condition = true;
if (condition)
{
       Console.WriteLine("La variable tiene un valor true");
}
else
{
       Console.WriteLine("La variable tiene un valor false.");
}

Analicemos el código. Si la variable es true, (verdadera), visualizaremos en pantalla el texto de “La variable tiene un valor true”, pero si el valor lo cambiáramos a false, veríamos el segundo mensaje “La variable tiene un valor false”. El contenido donde se dirigirá el flujo del programa, se encuentra encerrado entre llaves {}, a continuación tenemos la palabra clave else y otras dos llaves con el contenido donde se redirigirá el programa cuando la condición es false.

En el siguiente ejemplo, declaramos una variable con valor entero y le asignamos el valor 8 y en la instrucción comparamos ese valor m para ver si es mayor que 10, ¿es mayor 8 que 10 (entre paréntesis)? Yo creo que no, pues entonces se aparecerá el mensaje “La variable tiene un valor false”.

int m = 8;
if (m > 10)
{
        Console.WriteLine("La variable tiene un valor true");
}
else
{
        Console.WriteLine("La variable tiene un valor false.");
}

También podríamos comparar si un texto es igual a otro mediante el operador ==

string texto = “mi_texto”;
if (texto == “mi_texto”)
{
	Console.WriteLine("La variable tiene un valor true");
}

También existe una combinación de if else y es que además podemos hacer una comparativa if y si falla esta, podemos hacer de nuevo otra comparación else if y si fallaran todas las instrucciones else if, redirigir el programa a la instrucción else.

int m = 8;
if (m > 10)
{
	Console.WriteLine("La variable tiene un valor true");
}
else if (m == 8)
{
	Console.WriteLine("La variable tiene un valor igual a 8");
} 
else
{
	Console.WriteLine("La variable tiene un valor false.");
}

La instrucción if tiene una forma simplificada como la que os represento

if (m > 10) {Console.WriteLine("La variable tiene un valor true");}
else {Console.WriteLine("La variable tiene un valor false.");}

(Esta versión es igual que las anteriores, pero con las llaves en la misma línea)
o

if (m > 10) Console.WriteLine("La variable tiene un valor true");
else Console.WriteLine("La variable tiene un valor false.");

Consejo: cuando no sepáis como funciona una instrucción, acudid a la referencia de msdn, ahí tenéis todo.

Otra instrucción de selección es switch en la que se redirige el programa a la lista de candidatos, en el siguiente caso, como es igual a 1, se redirigirá a la zona donde se imprimirá en pantalla “Caso 1”. Si por ejemplo el valor de la variable tuviera un valor de 17, como no existe ningún caso que valga 17, se ejecutaría la instrucción Default.

La palabra clave break, hace que una vez ejecutado el caso concreto, el programa se redirija fuera de la instrucción y no ejecute nada más dentro de esta.

int casoSwitch = 1;
switch (casoSwitch)
{
            case 1:
                Console.WriteLine("Caso 1");
                break;
            case 2:
                Console.WriteLine("Caso 2");
                break;
            default:
                Console.WriteLine("Caso Default");
                break;
}

En el lenguaje “der Waki”. ¿Existe algún caso que sea igual a 2? Pués claro!! Ir hasta el caso 2 e imprimir en pantalla “Caso 2”.

Otro caso, sería poder agrupar casos. En este concreto, hemos agrupado el caso 2,3 y 8, de modo que si la variable vale alguno de ellos, aparecerá el texto “Caso 2, 3 u 8”

        int casoSwitch = 3;
        switch (casoSwitch)
        {
            case 1:
                Console.WriteLine("Caso 1");
                break;
            case 2:
            case 3:
            case 8:
                Console.WriteLine("Caso 2, 3 u 8");
                break;
            default:
                Console.WriteLine("Caso Default");
                break;
        }

En Visual Basic .NET es muy parecido todo, pero existen algunas variaciones, primero que no existen llaves como en C#, estas se cambian por palabras clave, entonces para iniciar una instrucción if, esta comienza por lo mismo If, se añade la condición (sin paréntesis), palabra clave Then (entonces en inglés), el contenido del código que se ejecutará cuando sea verdadero, ElseIf o Else cuando queremos efectuar nuevas comparativas o la condición es falsa respectivamente y por fin le decimos al compilador que la instrucción finaliza con End If. ¿Bien? Prácticamente es igual!

Dim m As Integer = 8
If m > 10 Then
	Console.WriteLine("La variable tiene un valor true")
ElseIf m = 8 Then
	Console.WriteLine("La variable tiene un valor igual a 8")
Else
	Console.WriteLine("La variable tiene un valor false.")
End If

Para la instrucción switch iniciamos con Select Case y expresión Switch. Luego los casos, código del caso y salida de la instrucción Select Case.

Dim casoSwitch As Integer = 3
Select Case casoSwitch
	Case 1
		Console.WriteLine("Caso 1")
		Exit Select
	Case 2, 3, 8
		Console.WriteLine("Caso 2, 3 u 8")
		Exit Select
	Case Else
		Console.WriteLine("Caso Default")
		Exit Select
End Select

Igual pero con otras palabras!! Same, but with other words!!
Y si encima .NET, te permite escribir en varios lenguajes, pues que más da el que uses!! el que más fácil te resulte, ¿no?

Saludos “der Waki”

Nota: Si utilizáis Visual Studio, escribid dentro de un método la palabra if y pulsáis la tecla TAB dos veces. OHHH!! el fragmento de código, se escribe solo!!!