miércoles, 26 de abril de 2023

C# 4 QUIZ GAME


 Nuestro siguiente juego es el famoso Quiz.

Se trata de un juego en el que vamos a tener una serie de preguntas con opciones y el usuario tendrá que elegir cuál cree que es la correcta.

Reglas:

·         Las preguntas tendrán 4 opciones como posibles respuestas donde sólo una es correcta.

·         Cada vez que el juego termina tendremos que mostrar en consola el nombre del usuario y los aciertos.

·         Al terminar el juego debe poder reiniciarse, otro usuario puede iniciar una partida y el programa debe mantener los scores de cada usuario y sus nombres.

Este programa debe estar programado bajo el paradigma de orientación a objetos POO, es decir TODO lo que puede ser tratado como una entidad tienen que tener una clase y debemos trabajar con instancias de esas clases.

Para hacer este ejercicio un poco más sencillo las preguntas no serán dinámicas serán hardcodeadas, más adelante ya usaremos bases de datos.

 

Vamos a crear un nuevo proyecto de consola que llamaremos QuizGame, con la consola como ya sabemos hacer

Esta vez creamos el proyecto ya con el IDE Visual Studio instalado. Una vez hemos creado un nuevo proyecto conel nombre de nuestro proyecto hacemos click derecho sobre él para crear una clase



Y podemos llamara a la clase Question


Al hacer esto se nos crea nuestra nueva clase junto a todas la librerías necesarias, estamos trabajando ya en Visual Studio, que no es lo mismo que Visual Studio Code.


Creamos una entidada para las preguntas, es decir cada una de la preguntas va a tener una clase, que luego me va a permitir instanciar cada pregunta individualmente.

El primer cambio que vamos a ahacer es hacer nuestra clase pública y dentro de esta primera clase vamos a tener 3 propiedades

public int Id { get; set; } esta propiedad int Id dispone de un get y un ser para obtener y setear el valor de int.
public string QuestionText { get; set; } y luego la pregunta en sí
tendríamos
namespace QuizGame
{
    public class Question
    {
        public int Id { get; set; }
        public string QuestionText { get; set; }
    }
}

Además digimos que cada pregunta tendrá 4 posibles opciones de respuesta, por lo que para esa entidad vamos a crear otra clase a la que vamos a llamar Option

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace QuizGame
{
    internal class Option
    {
        //Id de las respuestas
        public int Id {  get; set; }
        //La respuesta en si
        public string Text { get; set; }
        //determina cuál es la respuesta correcta
        public bool IsValid { get; set; }
    }
}

 

Una vez creada la clase options vamos a volver a la clase Question para agregar una nueva propiedad que va a se una colección de de Opciones con la palabra reservada List

List<Option>

Representa una lista fuertemente tipada de objetos a los que se puede acceder por índice. Proporciona métodos para buscar, ordenar y manipular listas.

namespace QuizGame
{
    public class Question
    {
        public int Id { get; set; }
        public string QuestionText { get; set; }
        public List<Option> Options { get; set; }
 
    }
}

Es decir, esta es la lista donde voy a tener todas las opciones de esta pregunta.

Vamos a crear una clase más que vamos a necesitar en el futuro que se llamará Answer

Podemos ver como nuestro proyecto ya tiene el archivo principal y las clases que hemos ido creando, podemos verlas en el menú lateral derecho y en las pestañas de navegación:


Pero vayamos a nuestra ultima clase Answer que también será pública y donde tendremos dos propiedades:

namespace QuizGame
{
    public class Answer
    {
        /*
         * Propiedad donde almacenaremos el Id de la pregunta
         * a la cuál es usuario está respondiendo
        */
        public int QuestionId { get; set; }
 
        //La opción que el usuario eligió
        public Option SelectedOption { get; set; }
    }

 

Entonces en mi entidad Answer voy a tener un Id de la pregunta y voy a tener el objeto de la respuesta.

Bien en este puto tenemos ya todas las clases que vamos a necesitar, todos nuestros objetos. Lo que vamos a hacer ahora es crear la preguntas, para ello volveremos a la pestaña Program.cs, como dijimos las preguntas de nuestro Quiz van a estar Hardcodeadas, mas adelante ya entraremos en programas más dinámicos.

Vamos a crear una variable global que llamaremos questions y la vamos a inicializar a una nueva lista.

Console.WriteLine("Quiz Game...");
 
// creo una variable global que guardará las preguntas en una lista
var questions = new
List<Questions>

Lo primero que pasa es que no me reconoce la clase questions,esto puede pasar con algunas ibrerías esto significa que debemos importar el using, si vamos a la lupa se nos sugerirá cual es la solución


Vemos que de entre algunas otras soluciones nos indica que agregemos el using

NOTA: La directiva using crea un alias para un espacio de nombres “namespace” o importa tipos definidos en otros espacio s de nombres.

le damos a agregar y ahora ya tenemos acceso a nuestra clase questions.

using QuizGame;
 
Console.WriteLine("Quiz Game...");
 
// creo una variable global que guardará las preguntas en una lista
var questions = new
List<Question>();

Vamos a crear un método de tiopo void que será un seed de perguntas, es decir vamos a crear en este método las preguntas que luego va aparecer opr pantalla.

Para ello invocamos nuestra clase Questions y le vamos dando los atributos que espera, que son el Id, eltexto de la pregunta y las opciones. Para ir añadiendo estas propiedades uaremos el método Add de las listas:

using QuizGame;
 
Console.WriteLine("Quiz Game...");
 
// creo una variable global que guardará las preguntas en una lista
var questions = new List<Question>();
 
//Creo un método de tipo void que será un seed de tipo questions
void SeddQuestionsAndOptions()
{
    questions.Add(new Question
    {
        Id = 1,
        QuestionText = "What is the biggest contry on earth?",
        //Ahora debo añadir las opciones pero recuerda que la opciones en la clase Question es tambien una lista
        Options = new List<Option>() 
        //Otra opción de añadir items a una lista además de Add, es abrir llaves y separar
        //Las opciones con comas ya que se la lista se está comportando así como un Array
        {
            new Option { Id = 1, Text = "Australia"},
            new Option { Id = 2, Text = "China"},
            new Option { Id = 3, Text = "Canada"},
            //Y recuerda que la clase Options tenemos la opción isValid para decidir cual es la correcta
            new Option { Id = 4, Text = "Russia", IsValid = true },
           
        }
    });
}

Repasemos que acabamos de hacer:

1.  Primero creé una lista de nombre Questions

2.  Usando el método Add agregé el primer item que es un item de tipo Question (clase Question)

3.  Ahora debo dar valores a las propiedades de Question (definidas en la clase Question, Id, Text Options <Lista>) Abro llaves y dentro de esas llaves separadas por comas puedo agregar las opciones id, Text, options donde options es también una lista, por lo que tengo que inicializarla también como la lista Optons. Y dentro de options repito lo que he hecho con questions le doy los valores a los atributos que en la clase options son Id, Text,  y IsValid para la opción que sea correcta (sólo una puede serlo). Solo necesito poner la opción isValid = true en la opción correcta ya que por defecto todos los valores booleanos están inicializados a flase.

Parece un poco complejo, pero nos iremos acostumbrando ya que esta es una delas maneras que tenemos para inicializar objetos complejos como éste.


Agregando más ítems a questions (más preguntas con sus opciones)

Repetimos el proceso anterior, para agregar tantas preguntas a nuestro Quiz como queramos

using QuizGame;
 
Console.WriteLine("Quiz Game...");
 
// creo una variable global que guardará las preguntas en una lista
var questions = new List<Question>();
 
//Creo un método de tipo void que será un seed de tipo questions
void SeddQuestionsAndOptions()
{
    questions.Add(new Question
    {
        Id = 1,
        QuestionText = "Which is the biggest contry on earth?",
        //Ahora debo añadir las opciones pero recuerda que la opciones en la clase Question es tambien una lista
        Options = new List<Option>() 
        //Otra opción de añadir items a una lista además de Add, es abrir llaves y separar
        //Las opciones con comas ya que se la lista se está comportando así como un Array
        {
            new Option { Id = 1, Text = "Australia"},
            new Option { Id = 2, Text = "China"},
            new Option { Id = 3, Text = "Canada"},
            //Y recuerda que la clase Options tenemos la opción isValid para decidir cual es la correcta
            new Option { Id = 4, Text = "Russia", IsValid = true }
           
        }
    });
 
    //Nueva question (repetimos el proceos para añadir las perguntas que queramos)
    questions.Add(new Question
    {
        Id = 2,
        QuestionText = "Which is the country  with the greatest population?",
        Options = new List<Option>()
        {
            new Option { Id = 1, Text = "India"},
            new Option { Id = 2, Text = "China", IsValid = true },
            new Option { Id = 3, Text = "United States"},
            new Option { Id = 4, Text ="Indonesia"}
        }
 
    });
 
}

Y así podemos agregar tantas preguntas como queramos , voy a copiar y a pegar para añadir una última pregunta:

using QuizGame;
 
Console.WriteLine("Quiz Game...");
 
// creo una variable global que guardará las preguntas en una lista
var questions = new List<Question>();
 
//Invocamos el métod de las preguntas
SeddQuestionsAndOptions();
 
//Creo un método de tipo void que será un seed de tipo questions
void SeddQuestionsAndOptions()
{
    questions.Add(new Question
    {
        Id = 1,
        QuestionText = "Which is the biggest contry on earth?",
        //Ahora debo añadir las opciones pero recuerda que la opciones en la clase Question es tambien una lista
        Options = new List<Option>() 
        //Otra opción de añadir items a una lista además de Add, es abrir llaves y separar
        //Las opciones con comas ya que se la lista se está comportando así como un Array
        {
            new Option { Id = 1, Text = "Australia"},
            new Option { Id = 2, Text = "China"},
            new Option { Id = 3, Text = "Canada"},
            //Y recuerda que la clase Options tenemos la opción isValid para decidir cual es la correcta
            new Option { Id = 4, Text = "Russia", IsValid = true }
           
        }
    });
 
    //Nueva question (repetimos el proceos para añadir las perguntas que queramos)
    questions.Add(new Question
    {
        Id = 2,
        QuestionText = "Which is the country  with the greatest population?",
        Options = new List<Option>()
        {
            new Option { Id = 1, Text = "India"},
            new Option { Id = 2, Text = "China", IsValid = true },
            new Option { Id = 3, Text = "United States"},
            new Option { Id = 4, Text ="Indonesia"}
        }
 
    });
 
    //Nueva question
    questions.Add(new Question
    {
        Id = 3,
        QuestionText = "Which was the less corrupt country in the world in 2021?",
        Options = new List<Option>()
        {
            new Option { Id = 1, Text = "Finland"},
            new Option { Id = 2, Text = "new Zealand"},
            new Option { Id = 3, Text = "Denmark" , IsValid = true },
            new Option { Id = 4, Text ="Norway"}
        }
 
    });
    //Nueva question
    questions.Add(new Question
    {
        Id = 4,
        QuestionText = "Which was the best country for living in 2021",
        Options = new List<Option>()
        {
            new Option { Id = 1, Text = "Norway" , IsValid = true },
            new Option { Id = 2, Text = "Belgium"},
            new Option { Id = 3, Text = "Sweden"},
            new Option { Id = 4, Text ="Spain"}
        }
 
    });
 
}

Con esto tenemos tenemos la 4 preguntas de nuestro Quiz listas. Así que lo que tenemos que hacer ahora es directamente invocar el método SeedQuestionsAndOption, ya lo hemos hecho lo puedes ver resaltado en amarillo en el código completo anterior.

Perfecto, una vez hecho lo anterior, minimizamos el método SeedQuestionsAndOptions, para ver todo un poco más organizado


Y vamos a crear un nuevo método también aquí en Program.cs  que será el método de inicialización del juego y al que vamos a llamar StartGame()

using QuizGame;
 
Console.WriteLine("Quiz Game...");
 
// creo una variable global que guardará las preguntas en una lista
var questions = new List<Question>();
 
//Invocamos el métod de las preguntas
SeedQuestionsAndOptions();
 
//Métod inicializador del juego
void StartGame()
{
 
}
 
//Creo un método de tipo void que será un seed de tipo questions
     void SeedQuestionsAndOptions()

Pensemos en qué cosas vamos a necesitar hacer en nuestro método principal, son cosas qiue ya hemos hecho en ejercicios anteriores:

  •          Presentar el juego al usuario
  •          Mantener el nombre del usuario
  •          Mostrar el nombre del usuario por consola antes de empezar el juego
  •          tenemos que crear una estructura de control para recorrer la lista de preguntas que acabamos de crear y mostrar por pantalla el texto de cada pregunta, el texto de cada una de las opciones de respuestas
  •          Tenemos que dejar el programa a la espera de la respuesta del usuario para cada pregunta

Vamos a nuestro método de inicio a hacer todo lo anterior. Empezaremos con un Console.writeLine (leer comentarios en verde)

void StartGame()
{
   Console.WriteLine("Are you ready? We are starting now¡");
    Console.WriteLine("What is your name?");
 
    //Guardamos el nombre enuna variable player y esperar que se introduzca el nombre
    var player = Console.ReadLine();
 
    //Mostrar el ombre del usuario or pantalla
    Console.Write($"ok, {player} let´s do this");
 
    //Usar una estructura de control para recorrer la lista de preguntas que acabamos de crear
    //y mostrar por pantalla el texto de cada pregunta, el texto de cada una de las opciones
    //de respuestas
 
}

Para crear esa estructura de control tenemos varias posibilidades, pero las dos más directas serían el for y el foreach. En este caso vamos a usar el foreach porque para recorrer listas que tienen instancias de clases como en este caso, el foreach nos resultará mucho más cómodo.

 foreach (var item in questions)
    {
 
    }

Vamos a recorrer la lista qestions tenemos la variable item donde se irá almacenando cada item de la lista en cada iteración, la iteración no termina mientras la lista tenga ítems.

Lo primero que haremos será imprimir la pregunta,m que como sabemos es la propiedad questionText:

  foreach (var item in questions)
    {
        //Impriminos en pantalla la pregunta
        Console.WriteLine(item.QuestionText);
 
    }

Una vez aparece la pregunta le indico al usario lo que tiene que hacer:

foreach (var item in questions)
    {
        //Impriminos en pantalla la pregunta
        Console.WriteLine(item.QuestionText);
 
        //Indicar al usuario que elija una respuesta
        Console.WriteLine("Please enter 1, 2, 3 or 4");
 
    }
}

Y a continuación debo imprimir las opciones de respuesta, como las opciones también son una lista usaremos otro foreach para la lista options (anidado). Recuerda options es una propiedad de cada item que recorremos, pero no podemos llamar ambien item al segundo bucle foreach lo vamos a cambiar por option como item de options,  que al final es la lista que estamos recorriendo

//Imprimimos las opciones de respuesta, para ello usaremos un foreach anidado
        //en el foreach principal
        foreach (var item in item.options)
        {
 
        }

 

    foreach (var item in questions)
    {
        //Impriminos en pantalla la pregunta
        Console.WriteLine(item.QuestionText);
 
        //Indicar al usuario que elija una respuesta
        Console.WriteLine("Please enter 1, 2, 3 or 4");
 
        //Imprimimos las opciones de respuesta, para ello usaremos un foreach anidado
        //en el foreach principal
        foreach (var option in item.options)
        {
 
        }
 
    }

Y ahora si imprimimos e número de la opción, para que el usuario sepa que opción elegir y la opción en sí misma usaremos interpolación de string

    foreach (var item in questions)
    {
        //Impriminos en pantalla la pregunta
        Console.WriteLine(item.QuestionText);
 
        //Indicar al usuario que elija una respuesta
        Console.WriteLine("Please enter 1, 2, 3 or 4");
 
        //Imprimimos las opciones de respuesta, para ello usaremos un foreach anidado
        //en el foreach principal
        foreach (var option in item.options)
        {
            Console.WriteLine($"{option.Id}. {option.Text}");
        }
 
    }
}

Vamos a ejecutar lo que tenemos hasta ahora para ver si funciona.

Invoco el método StartGame debajo de SeedQuestionsAndOptions y le damos a ejecutar, play verde recuerda que estamos en Visual Studio


Me pide el nombre


Se lo doy me muestra los mensajes y me imprime todas las preguntas luego termina la ejecución:


Luego hasta aquí todo está funcionando perfectamente

Necesitamos ahora capturar la opción que el usuario elija y validar que su respuesta sea válida, es decir un número entre el 1 y el 4 y tenemos que hacer todo esto sin detener la aplicación.

Para resolver lo que debemos hacer vamos a usar de nuevo la recursividad, primero vamos a crear un nuevo método que podemos llamar GetSelectedAnswer. Este método me va a devolver un string con el valor 1, 2,3 o 4 y el método se repetir tantas veces como haga falta hasta que el usuario responda.

string GetSelectedAnswer()
    //Método que devuelve un string con la respuesta 1,2,3,4
{
  var answer = Console.ReadLine();
 
    //Validamos que la respuesta no venga vacía y que sea una de las posibles opciones
  if(answer != null && (answer == "1") || (answer == "2") || (answer == "3") || (answer == "4"))
    {
        return answer;
    }
  else
    {
        Console.WriteLine("Please select a valid option");
    }
}

Pero si el usuario no ofrece una respuesta válida, además de lanzar el mensaje de error debemos darle de nuevo la oportunidad de contestar y es aquí donde otra vez hacemos uso de la recursividad, haciendo que el método se llame a sí mismo, y no olvides retornar la respuesta fuera del bloque else:

Esto se podría haber resuelto con un try catch.

string GetSelectedAnswer()
    //Método que devuelve un string con la respuesta 1,2,3,4
{
  var answer = Console.ReadLine();
 
    //Validamos que la respuesta no venga vacía y que sea una de las posibles opciones
  if(answer != null && (answer == "1") || (answer == "2") || (answer == "3") || (answer == "4"))
    {
        return answer;
    }
  else
    {
        Console.WriteLine("Please select a valid option");
        answer = GetSelectedAnswer();
    }
  return answer;
}

Una vez que tenemos creado nuestro método debemos usarlo en nuestro método principal StartGame, llamándolo justo después de mostrar las preguntas y las opciones de respuesta y guardándolo en una variable podemos llamar también answer:

void StartGame()
{
   Console.WriteLine("Are you ready? We are starting now¡");
    Console.WriteLine("What is your name?");
 
    //Guardamos el nombre enuna variable player y esperar que se introduzca el nombre
    var player = Console.ReadLine();
 
    //Mostrar el nombre del usuario or pantalla
    Console.Write($"ok, {player} let´s do this");
 
    //Usar una estructura de control para recorrer la lista de preguntas que acabamos de crear
    //y mostrar por pantalla el texto de cada pregunta, el texto de cada una de las opciones
    //de respuestas
    foreach (var item in questions)
    {
        //Impriminos en pantalla la pregunta
        Console.WriteLine(item.QuestionText);
 
        //Indicar al usuario que elija una respuesta
        Console.WriteLine("Please enter 1, 2, 3 or 4");
 
        //Imprimimos las opciones de respuesta, para ello usaremos un foreach anidado
        //en el foreach principal
        foreach (var option in item.Options)
        {
            Console.WriteLine($"{option.Id}. {option.Text}");
        }
 
        var answer = GetSelectedAnswer();
    }
}

En este punto, se han desplegado las preguntas del Quiz y se ha guardado la respuesta a cada opción que ha va dando el usuario, pero ¿qué vamos a hacer con cada respuesta? Necesitamos crear un histórico de cada repuesta , para ello necesitaremos crear una lista de ámbito global, del mismo modo que tenemos la lista de preguntas, vamos a tener una lista de respuestas que iremos rellenando con las respuestas del usuario.

using QuizGame;
 
Console.WriteLine("Quiz Game...");
 
// creo una variable global que guardará las preguntas en una lista
var questions = new List<Question>();
 
//Lista global para ir guardando las respuestas
var answers = new List<Answer>();

Lo que nos interesa ahora es crear otro método que se encargue de ir agregando las respuestas del usuario a la lista que acabamos de crear, lo podemos crear debajo del método GetSelectedAnswer y éste método va a recibir como parámetro la respuesta seleccionada que tenemos guardada en la variable answer del método principal  y también recibirá por parámetro el objeto Question, veamos cómo:

//Método que se guarda las respuestas del usuario en la lista answers
void AddAnswersToList(string answer, Question question)
{
 
}

Antes de crearlo vamos a invocarlo, lo invocaremos justo en el momento en le que el usuario de una respuesta válida a la pregunta, esta se recibe en el método principal StartGame (que es donde se invoca le método GetSelectedAnswer) y en ese momento guardamos la respuesta invocando nuestro nuevo método AddAnswerToList, así que vamos a StartGame y hacemos:

void StartGame()
{
   Console.WriteLine("Are you ready? We are starting now¡");
    Console.WriteLine("What is your name?");
 
    //Guardamos el nombre enuna variable player y esperar que se introduzca el nombre
    var player = Console.ReadLine();
 
    //Mostrar el nombre del usuario or pantalla
    Console.Write($"ok, {player} let´s do this");
 
    //Usar una estructura de control para recorrer la lista de preguntas que acabamos de crear
    //y mostrar por pantalla el texto de cada pregunta, el texto de cada una de las opciones
    //de respuestas
    foreach (var item in questions)
    {
        //Impriminos en pantalla la pregunta
        Console.WriteLine(item.QuestionText);
 
        //Indicar al usuario que elija una respuesta
        Console.WriteLine("Please enter 1, 2, 3 or 4");
 
        //Imprimimos las opciones de respuesta, para ello usaremos un foreach anidado
        //en el foreach principal
        foreach (var option in item.Options)
        {
            Console.WriteLine($"{option.Id}. {option.Text}");
        }
 
        var answer = GetSelectedAnswer();
        AddAnswersToList(answer, item);
    }
}

Recuerda que le método espera dos parámetros, la respuesta y el objeto Question, que es item (la pregunta en iteración) en este método.

Volvamos ahora a completar nuestro método AddAnswerToList, Lo que debo hacer es leer el string que me llega como parámetro (la respuesta), recordemos que la lista answers es de la clase Answer:

//Lista global para ir guardando las respuestas
var answers = new List<Answer>();

y la clase Answer tiene las propiedades:

·         QuestionId () que ya lo tengo por que ya recibo el objeto Question como parámetro

·         SelectedOption: tengo que ver como hago para obtener todo el objeto option, sólo basándo me en el string que recibo también por parámetro (la respuesta)

namespace QuizGame
{
    public class Answer
    {
        public int QuestionId { get; set; }
 
        //La opción que el usuario eligió
        public Option SelectedOption { get; set; }
    }
}

Entonces nuestro método AddAnswerToList, recibimos los parámetros, y vamos añadirlos a la lista answers

·         QuestionId ya lo tenemos ya que tenemos acceso al objeto question, solo debemos sacar su id (amarillo)

·         SelectedOption , ¿cómo accedo al objeto option ¿ TENDRÉ QUE CREAR UN NUEVO MÉTODO

void AddAnswersToList(string answer, Question question)
{
    //Añadir el Id de la pregunta y la respuesta a la lista answers del tipo Answer
    answers.Add(new Answer
    {
        QuestionId = question.Id,
        SelectedOption = crear un nuevo método
    });
}

para guardar la respuesta escogida debemos comparar el string de la respuesta que nos llega como parámetro, con la respuesta que coincide con ese número. Por eso creamos este nuevo método que llamamremos GetSelectedOption() y que además será un método del tipo Option:

lo creo justo debajo

//Método que se guarda las respuestas del usuario en la lista answers
void AddAnswersToList(string answer, Question question)
{
    //Añadir el Id de la pregunta y la respuesta a la lista answers del tipo Answer
    answers.Add(new Answer
    {
        QuestionId = question.Id,
        SelectedOption =
    });
}
 
Option GetSelectedOption(string answer, Question question)
{
 
}

Por supuesto este método también necesita recibir la respuesta y la pregunta, lo que tengo que hacer es recorrer todas las opciones que recibo en el objeto question y comparar el Id de cada opción con el id que me viene en answer:

Option GetSelectedOption(string answer, Question question)
{
    var selectedOption = new Option();
 
    //Recorrer todas las opciones de question
 
    return selectedOption;
}

 

La solución es recorrer con un foreach todas las opciones de las repuestas y cuando el Id de la opción se corresponda con el la respuesta del usuario (que parseamos ya que viene como string) esa es la respuesta opción que ha seleccionado el usuario y la retornamos:

Option GetSelectedOption(string answer, Question question)
{
    var selectedOption = new Option();
 
    //Recorrer todas las opciones de question
    foreach (var item in question.Options)
    {
        if (item.Id == int.Parse(answer))
             selectedOption = item;
    }
 
    return selectedOption;
}

Con el método terminado, solo nos queda invocarlo en e método anterior AddAnswerToList:

 

//Método que se guarda las respuestas del usuario en la lista answers
void AddAnswersToList(string answer, Question question)
{
    //Añadir el Id de la pregunta y la respuesta a la lista answers del tipo Answer
    answers.Add(new Answer
    {
        QuestionId = question.Id,
        SelectedOption = GetSelectedOption (answer, question)
    }); ;
}

En este momento ya tendríamo las respuestas del usuario almacenadas en la lista answersdel tipo Answers, esto nos va a servir para el Scoring, que era otro de los requisitos de este juego.

Cuando el usuario ha terminado de contestar las preguntas, es decir cuando el foreach del método principal ha llegado a su fin, debo mostrar cuantas respuestas válidas obtuvo el usuario, es decir cuantas veces selectedOption dentro de cada item answers tiene true es decir es la respuesta correcta. (recuerda que de toda las respuestas posibles una y solo una tiene la propiedad isValid a true)

void StartGame()
{
   Console.WriteLine("Are you ready? We are starting now¡");
    Console.WriteLine("What is your name?");
 
    //Guardamos el nombre enuna variable player y esperar que se introduzca el nombre
    var player = Console.ReadLine();
 
    //Mostrar el nombre del usuario or pantalla
    Console.Write($"ok, {player} let´s do this");
 
    //Usar una estructura de control para recorrer la lista de preguntas que acabamos de crear
    //y mostrar por pantalla el texto de cada pregunta, el texto de cada una de las opciones
    //de respuestas
    foreach (var item in questions)
    {
        //Impriminos en pantalla la pregunta
        Console.WriteLine(item.QuestionText);
 
        //Indicar al usuario que elija una respuesta
        Console.WriteLine("Please enter 1, 2, 3 or 4");
 
        //Imprimimos las opciones de respuesta, para ello usaremos un foreach anidado
        //en el foreach principal
        foreach (var option in item.Options)
        {
            Console.WriteLine($"{option.Id}. {option.Text}");
        }
 
        var answer = GetSelectedAnswer();
        AddAnswersToList(answer, item);
    }
  //Iterar la lista de respuestas (que ahora está llena) y encontrar cuantas respuestas     acertadas tubo el usuario
}

¿Como vamos a hacer esto? Pues creando otro método que podemos llamará GetScore() y será de tipo int. Lo vamos a invocar en su lugar incluso antes de crearlo, lo tenemos que invocar desde el método principal donde tenemos el comentario resaltado en amarillo:

void StartGame()
{
   Console.WriteLine("Are you ready? We are starting now¡");
    Console.WriteLine("What is your name?");
 
    //Guardamos el nombre enuna variable player y esperar que se introduzca el nombre
    var player = Console.ReadLine();
 
    //Mostrar el nombre del usuario or pantalla
    Console.Write($"ok, {player} let´s do this");
 
    //Usar una estructura de control para recorrer la lista de preguntas que acabamos de crear
    //y mostrar por pantalla el texto de cada pregunta, el texto de cada una de las opciones
    //de respuestas
    foreach (var item in questions)
    {
        //Impriminos en pantalla la pregunta
        Console.WriteLine(item.QuestionText);
 
        //Indicar al usuario que elija una respuesta
        Console.WriteLine("Please enter 1, 2, 3 or 4");
 
        //Imprimimos las opciones de respuesta, para ello usaremos un foreach anidado
        //en el foreach principal
        foreach (var option in item.Options)
        {
            Console.WriteLine($"{option.Id}. {option.Text}");
        }
 
        var answer = GetSelectedAnswer();
        AddAnswersToList(answer, item);
    }
 
    int score = GetScore();
}

Vamos ahora a crear este nuevo método, podemos crearlo al final del todo:


int GetScore()
{
 
}

Vamos a necesitar una variable  par aguardar la puntuación que podemos llamar score y que inicializaremos a cero y vamos a necesitar tambien un ciclo foreach


int GetScore()
{
  int score = 0;
 
 foreach( var item in answers)
    {
 
    }
 
}


Este foreach va a iterar en answers buscando si la propiedad Isvalid es true, es decir si la respuesta del usuario es la correcta entonces incremento el score y si no sencillamente no hacemos nada aparte de devolver la cuenta de score:

int GetScore()
{
  int score = 0;
 
 foreach( var item in answers)
    {
        if(item.SelectedOption.IsValid)
            score++;
    }
   return score;
 
}

Así de sencillo, como tenemos todas la respuestas en una lista puedo iterarla y ver cuales son las respuesta que el usario ha elegido y discriminar si son las correctas o no. Por cada acierto incremento el score y lo retorno al final.

Ahora que ya tengo el valor del score vuelvo al método principal StartGame y justo debajo de donde he invocado a GetScore, puedo mostrar por pantalla la información que ese método me devuelve:

 
    int score = GetScore();
    Console.WriteLine($"Nice try {player}! You answered well {score} questions"
);

En este momento tenemos el score de este jugador en particular, pero recordemos que uno de los requisitos del juego era mantener el histórico de cada uno de los jugadores, es decir de cada jugador debemos guardar nombre y score, para lo cual vamos a usar otro tipo de colección de datos, hasta este momento hemos usado listas, ahora vamos a usar un diccionario.

Introduciendo los diccionarios

Como hemos dicho el nombre y el score de cada jugador, lo vamos a guardar en un tipo de colección de datos que llamamos diccionarios, vamos a declarar nuestro primer diccionario al que vamos a llamar scores

 

 

//Diccionario para guardar todos los nombres de usuarios y sus scores
var scores = new Dictionary<string, int>();

un diccionario se declara como se puede ver en el código anterior, tiene dos valores

·         Key: en este caso de tipo string donde guardaré el nombre del jugador player

·         Value: en este caso de tipo int (el valor de score).

Disponiendo de esta estructura lo único que tengo que hacer es que cada vez que el juego termina, tengo que actualizar el diccionario, igual que hacemos con el método AddAnsweToList solo que vamos a añadirlo al diccionario, es un poco más complejo que le manejo de listas pero no demasiado.

Vamos al final del todo y crearemos un nuevo método que podemos llamar UpdateScore(). Como puedes ver todo lo estaos haciendo a través de métodos de forma que nos queda un código mas legible y sostenible:



Este nuevo método UpdateScore necesita recibir por parámetro el player y el score:

void UpdateScore(string player, int score)
{
 
}

Lo primero que vamos a hacer es no asumir que el diccionario está vacío, asumiremos que el diccionario existe y comprobaremos si el jugador actual ya ha jugado antes.

Para ello con un foreach vamos a iterar el diccionario (aunque ahora sabemos que está vacío)

//Método para actualizar y almacenar el diccionario jugador-score
void UpdateScore(string player, int score)
{
    foreach(var item in scores)
    {
        if(item.)
    }
 
}




Y observa como visual studio ya nos dice que tenemos acceso a las propiedades del diccionario, que se declararon al inicializarlo:

·         Value: string nombre del jugador

·         Int: score

Entonces si key es igual a player, es que este jugador ya ha jugado antes y lo que haré será actualizar su valor. A un diccionario se accede como sifuera un array claveàvalor:

void UpdateScore(string player, int score)
{
    foreach(var item in scores) //el jugador ya ha jugado antes así que actualizao el diccionario
    {
        if(item.Key == player)
        {
            scores[item.Key] = score;
        }
    }
 
}

Esto busca el item o jugador del diccionario por su key que és única y le asigna el valor de score que le pertenece a ese jugador. Las key de los diccionarios són únicas siempre, esta es una característica de los diccionarios.

Si esto no es así, es decir el jugador es nuevo, es la primera vez que juega, lo voy a agregar, pera ello necesitaré una variable local booleana para saber si estoy haciendo un update o un insert, así que me creo una variable que puedo llamar updated que inicializo a false:

void UpdateScore(string player, int score)
{
    foreach(var item in scores) //el jugador ya ha jugado antes así que actualizao el diccionario
    {
        bool uodated = false//Saber si hago update o insert
 
        if(item.Key == player)
        {
            scores[item.Key] = score;
        }
    }
 
}


Con esta variable, si entramos por el caso anterior de que el jugador no es nuevo, hago esta variable true y no tengo que hacer nada mas:

void UpdateScore(string player, int score)
{
    foreach(var item in scores) //el jugador ya ha jugado antes así que actualizao el diccionario
    {
        bool updated = false//Saber si hago update o insert
 
        if(item.Key == player)
        {
            scores[item.Key] = score;
            updated = true;
        }
    }

 

Y ahora el caso de que el jugador sea nuevo, debo agregarlo al diccionario:

void UpdateScore(string player, int score)
{
    foreach(var item in scores) //el jugador ya ha jugado antes así que actualizao el diccionario
    {
        bool updated = false//Saber si hago update o insert
 
        if(item.Key == player)
        {
            scores[item.Key] = score;
            updated = true;
        }
 
        if(!updated)  //El jugador es nuevo por lo tanto tengo que agregarlo al diccionario
        {
            scores.Add(player, score);
        }
    }
 
}

Una vez que tenemos esto, vamos a volver a nuestro método principal y solo tenemos que invocar a nuestro nuevo método UpdateScore() . Podemos hacerlo justo después de mostrale su score al jugador

void StartGame()
{
   Console.WriteLine("Are you ready? We are starting now¡");
    Console.WriteLine("What is your name?");
 
    //Guardamos el nombre enuna variable player y esperar que se introduzca el nombre
    var player = Console.ReadLine();
 
    //Mostrar el nombre del usuario or pantalla
    Console.Write($"ok, {player} let´s do this");
 
    //Usar una estructura de control para recorrer la lista de preguntas que acabamos de crear
    //y mostrar por pantalla el texto de cada pregunta, el texto de cada una de las opciones
    //de respuestas
    foreach (var item in questions)
    {
        //Impriminos en pantalla la pregunta
        Console.WriteLine(item.QuestionText);
 
        //Indicar al usuario que elija una respuesta
        Console.WriteLine("Please enter 1, 2, 3 or 4");
 
        //Imprimimos las opciones de respuesta, para ello usaremos un foreach anidado
        //en el foreach principal
        foreach (var option in item.Options)
        {
            Console.WriteLine($"{option.Id}. {option.Text}");
        }
 
        var answer = GetSelectedAnswer();
        AddAnswersToList(answer, item);
    }
 
    int score = GetScore();
    Console.WriteLine($"Nice try {player}! You answered well {score} questions");
 
    //Actualizamos el diccionario invocando al método
    UpdateScore(player, score);
 
}

Una vez que tenemos nuestro diccionario de scores y player actualizada lo único que nos queda es mostrar la lista de todos los scores, una vez que el usuario ha terminado la partida, puede ver cuál es su score y con esto va a poder compararlo con el resto de los scores de los demás jugadores.

Esto lo podemos hacer iterando todo el diccionario y mostrando por pantalla tdos los jugadores y sus puntuaciones. Lo vamos a resolver creando un nuevo método abajo del todo que será de tipo void y que podemos llamar ShowScores()

void ShowScores()
{
    Console.WriteLine("Scores:");
 
    foreach(var item in scores) //Iteramos el diccionario
    {
        Console.WriteLine($"{item.Key}, score: {item.Value}");
    }
}

Como puedes ver con un foreach itero el diccionario y saco por pantalla el nombre del jugador y su score

Como siempre lo único que nos resta hacer es invocar este nuevo método en nuestro método principal StartGame:

void StartGame()
{
   Console.WriteLine("Are you ready? We are starting now¡");
    Console.WriteLine("What is your name?");
 
    //Guardamos el nombre enuna variable player y esperar que se introduzca el nombre
    var player = Console.ReadLine();
 
    //Mostrar el nombre del usuario or pantalla
    Console.Write($"ok, {player} let´s do this");
 
    //Usar una estructura de control para recorrer la lista de preguntas que acabamos de crear
    //y mostrar por pantalla el texto de cada pregunta, el texto de cada una de las opciones
    //de respuestas
    foreach (var item in questions)
    {
        //Impriminos en pantalla la pregunta
        Console.WriteLine(item.QuestionText);
 
        //Indicar al usuario que elija una respuesta
        Console.WriteLine("Please enter 1, 2, 3 or 4");
 
        //Imprimimos las opciones de respuesta, para ello usaremos un foreach anidado
        //en el foreach principal
        foreach (var option in item.Options)
        {
            Console.WriteLine($"{option.Id}. {option.Text}");
        }
 
        var answer = GetSelectedAnswer();
        AddAnswersToList(answer, item);
    }
 
    int score = GetScore();
    Console.WriteLine($"Nice try {player}! You answered well {score} questions");
 
    //Actualizamos el diccionario invocando al método
    UpdateScore(player, score);
 
    //Mostrar los scores de todos los jugadores
    ShowScores();
 
}

Vamos a testear nuestro juego

Primero nos pregunta el nombre y nos muestra sólo una pregunta a la vez, esto es porque tenemos un console.writeline esperando respuesta y esto detiene la ejecución hasta recibir esa respuesta y es lo que debe ser


En el momento en que doy una respuesta debería pasara la siguiente pregunta y así es


Voy a introducir una respuesta que sea válida por ejemplo un 5 y efectivamente me muestra el mensaje de eeror y vuelve a esperar una respuesta válida


Si termino de jugar me muestra mi score y termina la ejecución


Sin embargo, en scores no me ha mostrado nada, debería haberme mostrado mi nombre y mi puntuación, debo tener algún error.

El error lo tenemos en el método Update score:

Primero la variable bool updated debería etar fuera y antes del foreach

Y la validación del updated debería estar fuera del foreach ya que como el foreach no tiene nada no itera

void UpdateScore(string player, int score)
{
    foreach(var item in scores) //el jugador ya ha jugado antes así que actualizao el diccionario
    {
        bool updated = false;  //Saber si hago update o insert
 
        if(item.Key == player)
        {
            scores[item.Key] = score;
            updated = true;
        }
 
        if(!updated)  //El jugador es nuevo por lo tanto tengo que agregarlo al diccionario
        {
            scores.Add(player, score);
        }
    }
 
}

Sería asi:

void UpdateScore(string player, int score)
{
    bool updated = false//Saber si hago update o insert
 
    foreach (var item in scores) //el jugador ya ha jugado antes así que actualizao el diccionario
    {
 
        if(item.Key == player)
        {
            scores[item.Key] = score;
            updated = true;
        }
    }
 
    if (!updated)  //El jugador es nuevo por lo tanto tengo que agregarlo al diccionario
    {
        scores.Add(player, score);
    }
 
}

Si ahora jugamos una partida, al teminar veremos el score y el nombre




Para finalizar lo último que nos quedaría sería dar la opción de volver a jugar de nuevo, para ello lo primero que necesitamos hacer es limpiar la lista de preguntas para que quede de nuevo vacía

 

Vamos al método principal StartGame y al final del todo cuando el juego ha teminado la inicializamos y agregaremos dos impresiones por consola para dar info al jugador:

    //VOLVER A JUGAR________________________________________
 
    //Limpiar la lista de preguntas
    answers = new List<Answer>();
 
    Console.WriteLine("Do you want to play again?");
    Console.WriteLine("Enter yes to play again or any other key to exit...");
 
    var playAgain = Console.ReadLine();
    if (playAgain.ToLower().Trim() == "yes")
        StartGame();
    //________________________________________________________

}Esta vez si el usuario no escribe yes el juego termina