Nueva funcionalidad Dilema prisionero

1.- Incorporar nuevas estrategias:

Espejo: Si el jugador contrario ha cooperado más que desertado, nosotros también cooperamos. En caso contrario, desertamos. Si hay empate o es la primera vez, cooperamos.

Contrario: Hacemos lo contrario de la última jugada del adversario, si ha cooperado, desertamos y viceversa.

2.- Diferentes matrices de pago

Poder incorporar variantes de la matriz de pago. Por ejemplo el juego ‘Gallina’ tiene la siguiente matriz de pagos:

Cooperar Desertar
Cooperar 5, 5 1, 10
Desertar 10,1 -20, -20

En el juego AmigoEnemigo la matriz de pagos es la siguiente:

Cooperar Desertar
Cooperar 1, 1 0, 2
Desertar 2, 0 0, 0

Podemos probar como cambia la efectividad de las estrategias en diferentes condiciones.

3.- El Juego no tiene constructor y todas las propiedades se inicializan en la declaración o hay que añadirlas a mano. Crear un constructor (puede tener parámetros por defecto) que solucione esta carencia, y que nos permita pasar otra matriz de pago.

¿Un return o varios?

La cosa no está clara:

https://stackoverflow.com/questions/36707/should-a-function-have-only-one-return-statement

Artículo sobre el tema:

https://www.anthonysteele.co.uk/TheSingleReturnLaw

Una buena práctica:

Minimize the number of returns in each routine. It’s harder to understand a routine if, reading it at the bottom, you’re unaware of the possibility that it returned somewhere above.

Use a return when it enhances readability. In certain routines, once you know the answer, you want to return it to the calling routine immediately. If the routine is defined in such a way that it doesn’t require any cleanup, not returning immediately means that you have to write more code.

Escribir en fichero


string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
StreamWriter outputFile = new StreamWriter(Path.Combine(docPath, "salida.txt")));
outputFile.WriteLine("Esto es una línea");
outputFile.WriteLine("Esto es otra");
outputFile.Close();

Kata: Dilema del prisionero

El Dilema del prisionero es un clásico de la teoría de juegos. El enunciado clásico es así:

<i>La policía arresta a dos sospechosos. No hay pruebas suficientes para condenarlos y, tras haberlos separado, los visita a cada uno y les ofrece el mismo trato. Si uno confiesa y su cómplice no, el cómplice será condenado a la pena total, diez años, y el primero será liberado. Si uno calla y el cómplice confiesa, el primero recibirá esa pena y será el cómplice quien salga libre. Si ambos confiesan, ambos serán condenados a seis años. Si ambos lo niegan, todo lo que podrán hacer será encerrarlos durante un año por un cargo menor.</i>

Lo curioso del caso es que la mejor estrategia conjunta es callarse, pero la mejor estrategia individual es confesar. El juego se ha transformado en una versión en la que los participantes ganan dinero dependiendo de lo que elijan. La matriz de pago suele ser la siguiente:

Cooperar Desertar
Cooperar 3, 3 -5, 5
Desertar 5, -5 -1, -1

Cada jugador elige de antemano lo que va a hacer. Si los dos han decidido cooperar gana cada uno 3€. Si los dos deciden desertar pierden cada uno 1€. Si uno deserta y el otro coopera el que deserta gana 5€ y el que coopera pierde 5€.

Como es imaginable hay muchas estrategias si tenemos que jugar varias veces este juego. Podemos cooperar siempre, desertar siempre, reaccionar a lo que haga el otro jugador, jugar al azar…

Para probar diferentes estrategias vamos a programar un juego que permita varios jugadores, cada uno con su estrategia, y los va a enfrentar uno a uno con la matriz de pagos puesta más arriba. Los enfrentamientos tienen 50 rondas cada uno. Al final de cada enfrentamiento los jugadores habrán ganado o perdido dinero. Se sumarán los importes y se mostrará una clasificación.

Por ejemplo, tenemos los jugadores Pánfilo, cuya estrategia es cooperar siempre, Maquiavelo, que siempre deserta y Tuntún, que elige al azar. Se enfrentarían siguiendo el esquema:

Maquiavelo-Pánfilo

Maquiavelo-Tuntún

Pánfilo-Tuntún

Cada enfrentamiento implica jugar 50 veces seguidas al dilema del prisionero. Por ejemplo, en Maquiavelo Pánfilo sería algo como esto:

Maquiavelo: Deserta | Pánfilo: Coopera (Maquiavelo gana 5€ y Pánfilo pierde 5€)

Maquiavelo: Deserta | Pánfilo: Coopera (Maquiavelo gana 5€ y Pánfilo pierde 5€)

(Así 50 veces) El resultado final es que Maquiavelo habrá ganado 250 € y Pánfilo habrá perdido 250 €

Una vez realizados todos los enfrentamientos se mostrará el ranking por dinero obtenido.

Para la realización del ejercicio pensad primero en el enfoque: Qué clases vais a implementar, si conviene utilizar interfaces, etcétera.

Inyección de dependencia

Las interfaces nos permiten desacoplar las funciones al no depender de una clase, sino de la implementación de un interfaz:

https://anexsoft.com/ejemplo-de-inyeccion-de-dependencias-con-c

Definición:

https://www.freecodecamp.org/news/a-quick-intro-to-dependency-injection-what-it-is-and-when-to-use-it-7578c84fa88f/

https://www.dotnettricks.com/learn/dependencyinjection/implementation-of-dependency-injection-pattern-in-csharp

Gente que está en contra (en ocasiones):

Las ventajas de NO usar inyección de dependencias

Otro ejemplo polimorfismo

 class Persona
    {
        private string _nombre;

        public Persona(string nombre)
        {
            Console.WriteLine("Constructor persona " + nombre);
            this.Nombre = nombre;
        }
        public string Nombre
        {
            set { this._nombre = value; }
            get { return this._nombre; }
        }
        public virtual string saludo()
        {
            return "Hola "+Nombre+" que tal";
        }
      
    }

    class Empleado :Persona
    {
        public Empleado(string nombre, int sueldo=0) : base(nombre)
        {
            Console.WriteLine("Constructor empleado " + nombre);

            this.Cargo = "Empleado";
        }
        public int Sueldo { get; set; }
        private string _cargo;
        public string Cargo {
            set { this._cargo = value; }
            get { return this._cargo; } }
        public override string saludo()
        {
            return "Hola empleado " + Nombre + " ¿Todo bien?";
        }
        public virtual double sueldoNeto()
        {
            return Sueldo * .85;
        }
       
    }
    class Gerente : Empleado
    {
        public int Bono { get; set; }
        public int Dietas { get; set; }
        public Gerente(string nombre) : base(nombre)
        {
            Console.WriteLine("Constructor gerente " + nombre);

            this.Cargo = "Gerente";

        }

        public Gerente(string nombre, int sueldo) : base(nombre, sueldo) { }
        public Gerente(string nombre, int sueldo, int dietas) : this(nombre, sueldo)
        {
            Dietas = dietas;
        }
        public override string saludo()
        {
            return "Hola Sr. " + Nombre + " ¿Desea alguna cosa?";
        }
        public override double sueldoNeto()
        {
            return base.sueldoNeto()+Dietas;
        }
    }
    sealed class Direccion : Gerente
    {
        public int StockOptions { get; set; }
        public Direccion(string nombre) : base(nombre, 100) {
            Console.WriteLine("Constructor direccion " + nombre);
        }
        public Direccion(string nombre, int sueldo) : base(nombre, sueldo) { }
        public Direccion(string nombre, int sueldo, int dietas):base(nombre, sueldo, dietas) { }
        public Direccion(string nombre, int sueldo, int dietas, int stockoptions) : this(nombre, sueldo, dietas)
        {
            this.StockOptions = stockoptions;
        }
        public override string saludo()
        {

            return "Buenos días Sr. " + Nombre + " Estamos a sus órdenes";
        }
        public override double sueldoNeto()
        {
            return base.sueldoNeto()+StockOptions*.5;
        }
    }
    class Cliente : Persona
    {
        public Cliente(string nombre) : base(nombre)
        {
        }
    }

Uso:

  Empleado eva = new Empleado("Eva",1200);
         
           
            Gerente ana = new Gerente("Ana");
            ana.Sueldo = 1700;
            ana.Dietas = 200;
            Direccion rosa = new Direccion("Rosa");
            rosa.Sueldo = 2500;
            rosa.Dietas = 300;
            rosa.StockOptions = 100;
            Console.WriteLine(rosa.sueldoNeto());
            Empleado[] plantilla = { eva, ana, rosa };
            double total = 0;
            foreach(Empleado item in plantilla)
            {
                total += item.sueldoNeto();
            }
            Console.WriteLine(total);

Herencia en c# y polimorfismo

 class Persona
    {
        private string _nombre;

        public Persona(string nombre)
        {
            Console.WriteLine("Constructor persona " + nombre);
            this.Nombre = nombre;
        }
        public string Nombre
        {
            set { this._nombre = value; }
            get { return this._nombre; }
        }
        public virtual string saludo()
        {
            return "Hola "+Nombre+" que tal";
        }

    }

    class Empleado :Persona
    {
        public Empleado(string nombre) : base(nombre)
        {
            Console.WriteLine("Constructor empleado " + nombre);

            this.Cargo = "Empleado";
        }
        private string _cargo;
        public string Cargo {
            set { this._cargo = value; }
            get { return this._cargo; } }
        public override string saludo()
        {
            return "Hola empleado " + Nombre + " ¿Todo bien?";
        }

    }
    class Gerente : Empleado
    {
        public int Bono { get; set; }
        public Gerente(string nombre) : base(nombre)
        {
            Console.WriteLine("Constructor gerente " + nombre);

            this.Cargo = "Gerente";

        }
        public Gerente(string nombre, int bono) : this(nombre)
        {
            this.Bono = bono;

        }
        public override string saludo()
        {
            return "Hola Sr. " + Nombre + " ¿Desea alguna cosa?";
        }
    }
    sealed class Direccion : Gerente
    {
        public Direccion(string nombre) : base(nombre, 100) {
            Console.WriteLine("Constructor direccion " + nombre);
        }
        public override string saludo()
        {
            return "Buenos días Sr. " + Nombre + " Estamos a sus órdenes";
        }
    }

Ejemplo de uso:

  Persona[] plantilla = { new Persona("Eva"), new Empleado("Juan"),new Empleado("Ana"),
            new Gerente("Rosa"), new Gerente("Luis"), new Direccion("Laura")};
            foreach(Persona empleado in plantilla)
            {
                Console.WriteLine(empleado.saludo());
            }

Clase Empleado

 class Empleado
    {
        private string pNombre;
        private string pDni;
        private decimal pSueldo;

        public string Nombre
        {
            get
            {
                return pNombre;
            }
            set
            {
                    Console.WriteLine("Set de nombre");
                if (value.Length < 3)
                {
                    throw new Exception("El nombre debe tener una longitud mayor de 3");
                }
                else
                {
                    this.pNombre = value;
                }
            }
        }
        public string Dni
        {
            get { return this.pDni; }
            set { this.pDni = value; }
        }
        public decimal Sueldo
        {
            get { return this.pSueldo; }
            set { this.pSueldo = value; }
        }
        public Empleado(string nombre)
        {
            Console.WriteLine("Constructor 1");
            this.Nombre = nombre;
        }
        public Empleado(string nombre, string dni) : this(nombre)
        {
            Console.WriteLine("Constructor 2");
            this.Dni = dni;
        }
        public Empleado(string nombre, string dni, decimal sueldo) : this(nombre, dni)
        {
            Console.WriteLine("Constructor 3");
            this.Sueldo = sueldo;
        }
    }