41. Una tabla de records.

En casi cualquier juego existe una "tabla de records", que guarda las mejores puntuaciones. Las características habituales son:

  • Se guarda una cantidad prefijada de puntuaciones (típicamente 10).
  • Para cada puntuación, se guarda también el nombre (o las iniciales) del jugador que la consiguió.
  • Las puntuaciones (y sus nombres asociados) deben estar ordenadas, desde la más alta a la más baja.
  • Las puntuaciones deben guardarse en fichero automáticamente cuando termina la partida (o incluso antes, cuando se introduzca un nuevo record).
  • Las puntuaciones se deben leer de fichero cada vez que el juego se pone en marcha. Cada puntuación nueva que se intenta introducir deberá quedar en su posición correcta, desplazando "hacia abajo" a las que sean inferiores, o incluso no entrando en la tabla de records si se trata de una puntuación más baja que todas las que ya existen en ella.
  • Para conseguirlo, podemos crear una clase auxiliar, que también se podrá aplicar a cualquier otro juego. Esa clase debería tener un método para "Cargar" desde fichero (o el constructor se podría encargar), otro para "Grabar" en fichero, otro para "Introducir" un nuevo record (su puntuación y su nombre asociado) y otro para "Obtener" un record (tanto la puntuación como el nombre, de modo que las podamos mostrar en pantalla).

Una forma sencilla de conseguirlo, usando simplemente un array para los puntos y otro distinto para los nombres, en vez de un array de struct o de objetos, podría ser ésta:

using System;
using System.IO;
 
public class TablaRecords
{
 
    const int NUM_RECORDS = 10;
    int[] puntos;
    string[] nombres;
 
    public TablaRecords()
    {
        puntos = new int[NUM_RECORDS];
        nombres = new string[NUM_RECORDS];
        Cargar();
    }
 
    public void Cargar()
    {
        if (!File.Exists("records.dat"))
            return;
 
        StreamReader fichero = File.OpenText("records.dat");
 
        for (int i = 0; i < NUM_RECORDS; i++)
        {
            nombres[i] = fichero.ReadLine();
            string puntuacion = fichero.ReadLine();
            if (puntuacion != null)
                puntos[i] = Convert.ToInt32(puntuacion);
        }
        fichero.Close();
    }
 
    public void Grabar()
    {
        StreamWriter fichero = File.CreateText("records.dat");
        for (int i = 0; i < NUM_RECORDS; i++)
        {
            fichero.WriteLine(nombres[i]);
            fichero.WriteLine(puntos[i]);
        }
        fichero.Close();
    }
 
    public int GetNumRecords()
    {
        return NUM_RECORDS;
    }
 
    public void Introducir(int puntuacion, string nombre)
    {
        // Compruebo "si cabe"
        if (puntos[NUM_RECORDS - 1] > puntuacion)
            return;
 
        // Busco la posición correcta
        int posicion = NUM_RECORDS - 1;
        while ((posicion > 0) && (puntuacion > puntos[posicion-1]))
        {
            posicion--;
        }
        // Inserto en esa posición (desplazando siguientes)
        for (int i = NUM_RECORDS - 1; i > posicion; i--)
        {
            puntos[i] = puntos[i - 1];
            nombres[i] = nombres[i - 1];
        }
        puntos[posicion] = puntuacion;
        nombres[posicion] = nombre;
 
        // Guardar cambios
        Grabar();
    }
 
    public void Obtener(int posicion, out int puntuacion,
                        out string nombre)
    {
        puntuacion = puntos[posicion - 1];
        nombre = nombres[posicion - 1];
    }
}
 

Y para probarla, lo razonable sería hacerlo "desde fuera del juego", con un programa específico, incluso en modo texto, que fuera añadiendo datos y tomándolos de la tabla de records, para comprobar de la forma más simple y a la vez más exhaustiva que se comporta correctamente:

using System;
 
public class PruebaTablaRecords
{
 
    public static void Main()
    {
 
        TablaRecords t = new TablaRecords();
 
        t.Introducir(200, "Juan");
        t.Introducir(100, "Pedro");
        t.Grabar();
        t.Introducir(150, "Alberto");
        t.Cargar();
 
        int maximo = t.GetNumRecords();
        for (int i = 1; i <= maximo; i++)
        {
            string nombre;
            int puntos;
            t.Obtener(i, out puntos, out nombre);
            Console.WriteLine("{0}: {1} = {2}",
                              i, nombre, puntos);
        }
 
    }
}
 

Las llamadas a "Cargar" y "Grabar" desde el programa de prueba son sólo para asegurarnos de que no "pasan cosas raras". En una implementación real, una vez que todo esté probado, sería razonable que tanto "Cargar" como "Grabar" fueran métodos privados, sólo para uso de la propia clase "TablaRecords" y no desde fuera de ella.

Ejercicio propuesto: Incluye esta clase en el juego. Añade cada record a la tabla al final de una partida (con un nombre prefijado). Crea una pantalla similar a la de "Créditos" para mostrar el contenido de la tabla de records.