En uno de mis blogs, en Arithmos, publiqué un post sobre una historia de carceleros y presos; minutos después de acabar el artículo, dije, ¿por qué no hago una app que lo muestre?, dicho y hecho, me puse y en una tarde desarrollada estaba.
Y ¿por qué digo esto? para demostrar lo fácil que es desarrollar una pequeña aplicación con C# y WPF.
Si leen el artículo, podrán observar que la aplicación solo muestra un tablero de ajedrez, con monedas en cada casilla con una cara o una cruz aleatoriamente, un cuadro mágico que es el elegido por el carcelero y un cuadro del tablero que debe calcular uno de los presos para que el siguiente convicto les salve de la muerte, este último cuadro no es otro que una operación XOR.
El modelo podría ser una clase Chessboard que alberga objetos del tipo CoinBox, las monedas que ya de camino le diremos que color tiene el cuadro donde se apoyan en el tablero.
public class Chessboard
{
public List CoinsBox { get; set; }
public Chessboard() : this(-1) { }
public Chessboard(int headsCount)
{
CoinsBox = new List();
FillChessboard(headsCount);
}
///
/// Rellena el tablero con cuadros y monedas aleaotorias con cara y cruz
///
/// Número de caras. si es -1, se genera aletoriamente
void FillChessboard(int headsCount)
{
var random = new Random();
headsCount = headsCount == -1 ? random.Next(64) : headsCount;
// Llenar Lista con monedas con cruz
Enumerable.Range(0, 64).ToList()
.ForEach(n => CoinsBox.Add(new CoinBox(n, false, (n / 8) % 2 == 0 ? n % 2 == 0 : n % 2 != 0)));
// Cambiar n monedas a cara
Enumerable.Range(0, headsCount).ToList().ForEach(n => CoinsBox[random.Next(64)].IsHeads = true);
// Cuadro mágico
CoinsBox[random.Next(64)].IsMagicBox = true;
// Cuadro que debe cambiar
var changed= CoinsBox
.Where(n => n.IsHeads).Aggregate(MagicBox.Index, (acum, n) => acum ^ n.Index, op => op);
CoinsBox[changed].IsChangedBox = true;
}
public CoinBox MagicBox => CoinsBox.Where(n => n.IsMagicBox).FirstOrDefault();
public CoinBox ChangedBox => CoinsBox.Where(n => n.IsChangedBox).FirstOrDefault();
public int ParityChessboard => CoinsBox
.Where(n => n.IsHeads).Aggregate(0, (acum, n) => acum ^ n.Index, op => op);
public string ParityChessboardToString => $"La paridad del tablero es {ParityChessboard}";
public string ResultToString => $"Si cambiamos la moneda {ChangedBox.Index}, la paridad del tablero será {MagicBox.Index} que es el cuadrado mágico!!!";
public string MagicBoxToString => $"El cuadro mágico seleccionado por el carcelero es el número {MagicBox.Index}";
public string ChangeBoxToString => $"La moneda que debe cambiar el convicto es la número {ChangedBox.Index}";
public string ListaXORToString => $"La operación XOR de las monedas que tienen cara {string.Join(", ", CoinsBox.Where(k => k.IsHeads).Select(i => i.Index))} y el cuadro mágico {MagicBox.Index} da como resultado {ChangedBox.Index} o la operación XOR entre el cuadro mágico {MagicBox.Index} y la paridad del tablero {ParityChessboard} es {ChangedBox.Index}";
}
public class CoinBox
{
public CoinBox(int index, bool isHeads, bool isBlack)
{
Index = index;
IsHeads = isHeads;
IsBlack = isBlack;
}
public int Index { get; set; }
public bool IsHeads { get; set; }
public bool IsTails { get { return !IsHeads; } }
public bool IsChangedBox { get; set; }
public bool IsMagicBox { get; set; }
public bool IsBlack { get; set; }
}
Como algo particular, la asignación del color del cuadro mediante la operación (n / 8) % 2 == 0 ? n % 2 == 0 : n % 2 != 0)
con la que con el índice del cuadro le decimos si el cuadro es negro o blanco o las expresiones lambda que calculan la operación XOR sobre una lista de elementos mediante la función lambda Aggregate
, la cual le pasamos un parámetro acumulador inicial y posteriormente es usado elemento a elemento con la operación XOR.Para la vista, he creado un control definido por el usuario llamado Box
, el cual contiene el Binding
cambiando su aspecto según las propiedades del objeto CoinBox
asociado a su Datacontext
. Como podéis ver a continuación, creo converter para transformar valores booleanos en Visibility, Brush o Bitmap. (Si alguien los quiere los converter, los cuelgo)
<UserControl x:Class="DxorAlive.Box"
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:DxorAlive"
mc:Ignorable="d"
d:DesignHeight="43" d:DesignWidth="43" Margin="1">
<UserControl.Resources>
<local:BoolToVisibleConverter x:Key="b2tvc" Reverse="False"/>
<local:ImageConverter x:Key="itc"/>
<local:BoolToColorConverter x:Key="btcc"/>
</UserControl.Resources>
<Grid>
<Rectangle Width="64" Height="64" Stroke="Gray" StrokeThickness="1" Fill="{Binding IsBlack, Converter={StaticResource btcc}}"/>
<Rectangle Width="62" Height="62" Stroke="Red" StrokeThickness="2" Visibility="{Binding IsMagicBox, Converter={StaticResource b2tvc}}"/>
<Rectangle Width="60" Height="60" Stroke="Orange" StrokeThickness="2" Visibility="{Binding IsChangedBox, Converter={StaticResource b2tvc}}"/>
<Image Source="{Binding IsHeads, Converter={StaticResource itc}}" Height="48"/>
</Grid>
</UserControl>
Ahora solo nos queda crear o con xml o desde la clase de la vista (runtime) una matriz de objetos cada uno con su datacontext. Yo lo he he desarrollado con código en vez de crear 64 cuadros del tipo Box. Llamamos al método FillChessboard
y se genera un nuevo tablero.
private void FillChessboard(int headsCount)
{
grid.Children.Clear();
// Crear tablero
var cb = new Chessboard(headsCount);
//crear cuadros y monedas
cb.CoinsBox.ForEach(n =>
{
var c = new Box();
c.DataContext = n;
var row = n.Index / 8;
var col = n.Index % 8;
Grid.SetRow(c, row);
Grid.SetColumn(c, col+1);
grid.Children.Add(c);
});
this.DataContext = cb;
}
he aquí el resultado. Un saludo a tod@s

Os dejo el enlace del ejecutable
PD: Y como llevo una temporada enamorado de Kotlin, os paso código en este lenguaje para una app de consola. I ♥ kt
import kotlin.random.Random
fun main(args: Array) {
val c= Chessboard(5)
println(c)
}
class Chessboard(var headsCount: Int) {
val coinsBox = mutableListOf()
init {
headsCount = if (headsCount == -1) Random.nextInt(64) else headsCount
fillChessboard()
}
private fun fillChessboard() {
// Rellenar de monedas
(0..63).toList().forEach {
coinsBox.add(CoinBox(it, false, if ((it / 8) % 2 == 0) (it % 2 == 0) else (it % 2 != 0)))
}
// Caras
(0..headsCount!!).toList().forEach { coinsBox[Random.nextInt(64)].isHeads = true }
// cuadro mágico
val vMB = Random.nextInt(64)
coinsBox[vMB].isMagicBox = true
// moneda cambiante
coinsBox[coinsBox.filter { i-> i.isHeads }.fold(vMB) { acc, opXOR -> acc.xor(opXOR.index) }].isChangedBox=true
}
override fun toString(): String {
return "$magic\n$parityToString\n$changed\n$result\n$listaXOR\n$chessboard"
}
val magicBox = coinsBox.filter { n -> n.isMagicBox }.firstOrNull()
val changedBox = coinsBox.filter { n -> n.isChangedBox }.firstOrNull()
val parityChessBoard = coinsBox[coinsBox.filter { i-> i.isHeads }.fold(0) { acc, opXOR -> acc.xor(opXOR.index) }]
val parityToString= "La paridad del tablero es ${parityChessBoard.index}"
val result = "Si cambiamos la moneda ${changedBox?.index}, la paridad del tablero será ${magicBox?.index} que es el cuadrado mágico"
val magic = "El cuadro seleccionado por el carcelero es el número ${magicBox?.index}"
val changed = "La moneda que debe cambiar el convicto es la número ${changedBox?.index}"
val listaXOR = "La operación XOR de las monedas que tienen cara ${coinsBox.filter { k-> k.isHeads }.joinToString(", "){it.index.toString()}} " +
"y el cuadro mágico ${magicBox?.index} da como resultado ${changedBox?.index} " +
"\no la operación XOR entre el cuadro mágico ${magicBox?.index} y la paridad del tablero ${parityChessBoard.index} es ${changedBox?.index}"
val chessboard = coinsBox.joinToString("") {k-> "\t${k.index} ${if (k.isHeads) "H" else "T"} ${if ((k.index+1) % 8==0) "\n" else ""}" }
}
data class CoinBox(
val index: Int,
var isHeads: Boolean,
var isBlack: Boolean,
var isChangedBox: Boolean = false,
var isMagicBox: Boolean = false