38. Una lista con las mejores puntuaciones. Cómo guardarla y recuperarla. (*)

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, como hicimos con la consola de depuración, 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 mostrr en pantalla).


Vamos con ello...

El fichero de cabecera podría ser así:

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*   TablaRecords.h:            */
/*     Clase "TablaRecords"     */
/*     Fichero de cabecera      */
/*                              */
/*  Comprobado con:             */
/*  - DevC++ 4.9.9.2(gcc 3.4.2) */
/*    y Allegro 4.03 - WinXP    */
/*------------------------------*/
 
#ifndef TablaRecords_h
#define TablaRecords_h
 
class TablaRecords {
 
    int cantidad;
 
 
 public:
 
    TablaRecords();
    TablaRecords(int tamanyo);
 
    void insertar(char* nombre, int puntos);
    void guardar();
    void leer();
 
    int getCantidad();
    char* getNombre( int i );
    int getPuntuacion( int i );
 
};
#endif
 

Y el desarrollo podría ser:

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*   TablaRecords.cpp:          */
/*     Clase "TablaRecords"     */
/*     Fichero de desarrollo    */
/*                              */
/*  Comprobado con:             */
/*  - DevC++ 4.9.9.2(gcc 3.4.2) */
/*    y Allegro 4.03 - WinXP    */
/*------------------------------*/
 
#include "TablaRecords.h"
#include <fstream>
using namespace std;
 
const int MAXRECORD = 10;
int tamanyo;
string *nombres;
int *puntuaciones;
 
 
TablaRecords::TablaRecords()
{
    int i;
    tamanyo = MAXRECORD;
 
    nombres = new string[tamanyo];
    for (i=0; i<tamanyo; i++)
		nombres[i] = " ";
 
    puntuaciones = new int[tamanyo];
    for (i=0; i<tamanyo; i++)
		puntuaciones[i] = 0;
}
 
 
TablaRecords::TablaRecords(int nuevoTamanyo)
{
    int i;
    tamanyo = nuevoTamanyo;
 
    nombres = new string[tamanyo];
    for (i=0; i<tamanyo; i++)
		nombres[i] = " ";
 
    puntuaciones = new int[tamanyo];
    for (i=0; i<tamanyo; i++)
		puntuaciones[i] = 0;
 
}
 
 
void TablaRecords::insertar(char* nombre, int puntos)
{
    int posicion = 0;
    int i;
 
    // Primero busco la posicion
    for (i=tamanyo-1; i >= 0; i--)
        if (puntos > puntuaciones[i])
			posicion = i;
 
    // Y despues desplazo a partir de ella
    for (i=tamanyo-2; i >= posicion; i--)
    {
        puntuaciones[i+1] = puntuaciones[i];
        nombres[i+1] = nombres[i];
    }
 
    puntuaciones[posicion] = puntos;
    nombres[posicion] = nombre;
}
 
 
void TablaRecords::guardar()
{
    fstream ficheroRecords;
    ficheroRecords.open ( "records.dat" , ios::out);
    for (int i=0; i < tamanyo; i++)
    {
        ficheroRecords << nombres[i];
        ficheroRecords << endl;
        ficheroRecords << puntuaciones[i];
        ficheroRecords << endl;
    }
    ficheroRecords.close();
}
 
 
void TablaRecords::leer()
{
	// Propuesto como ejercicio
}
 
 
int TablaRecords::getCantidad()
{
    return tamanyo;
}
 
 
char* TablaRecords::getNombre( int i )
{
	if (i < tamanyo)
		return const_cast<char *>( nombres[i].c_str() );
	else 
		return " ";
}
 
 
int TablaRecords::getPuntuacion( int i )
{
	if (i < tamanyo)
		return puntuaciones[i];
	else 
		return 0;
}
 
 

Para probarlo, no es necesario hacerlo desde dentro de un juego. De hecho, puede ser preferible crear un pequeño programa de prueba, que introduzca datos, los lea, los guarde en disco, etc. Podría ser algo como:

// Prueba de la clase TablaRecords
 
#include "TablaRecords.h"
#include <iostream>
using namespace std;
 
int main()
{
    int i;
    TablaRecords *tr;
 
    tr = new TablaRecords(8);
 
    cout << "Tabla de records vacia:\n";
 
    for (i=0; i < tr->getCantidad(); i++)
        cout << tr->getNombre(i) << " : "
             << tr->getPuntuacion(i) << "\n";
 
    tr->insertar("nombre1", 50);
    tr->insertar("nombre2", 100);
    tr->insertar("nombre3", 75);
    tr->insertar("nombre4", 200);
 
    cout << "Tabla de records con datos:\n";
 
    for (i=0; i < tr->getCantidad(); i++)
        cout << tr->getNombre(i) << " "
             << tr->getPuntuacion(i) << "\n";
 
    return 0;
}
 

Este programa daría un resultado como:


Tabla de records vacia:
  : 0
  : 0
  : 0
  : 0
  : 0
  : 0
  : 0
  : 0
Tabla de records con datos:
nombre4 200
nombre2 100
nombre3 75
nombre1 50
  0
  0
  0
  0

Todavía no es totalmente aplicable a nuestros juegos, porque aún no tenemos ninguna forma de que el usuario introduzca su nombre, de modo que sólo podríamos guardar y mostrar puntuaciones numéricas. Más adelante lo solucionaremos...