Highscore speichern



  • Vllt hilft es dir auch zuerst ein Paar Gedanken zu machen. Z.B. wie deine Score-Datei aussehen soll.
    Ich denke eine einfache Lösung wäre folgender Aufbau:

    Tom:8
    Spieler 2:7
    Max:5
    

    Dann liest du die Datei ein:

    map<string,int>	name_score;							//brauchen wir später...
    
    ifstream in("score.txt");							//Textdatei wird geöffnet
    if(!in)										//Öffnen schlägt fehl
    {
    	/*Fehler verarbeiten, z.B. mit entsprechender Ausgabe*/
    }
    else
    {
    	while(!in.eof())							//Alle Zeilen der Textdatei lesen
    	{
    		string line;							//In string line kommt jeweils zu bearbeitende Zeile
    		getline(in, line);						//Die Zeile wird aus dem ifstream geholt
    
    		if(line.empty() || line[0] = c	)				//Ist die Zeile leer oder auskommentiert, wobei char c = '#', z.B. ein Zeichen für eine Kommentarzeile
    			continue;						//In die nächste Zeile...
    
    		string::size_type split = line.find(":");			//Die Zeile beim ":" zerteilen (!!! D.H. DIE NICKNAMES DÜRFEN KEIN ":" ENTHALTEN!!!)
    		if(split == string::npos)					//":" wurde nicht gefunden...
    			continue;						// ... Zeile also fehlerhaft und wird übersprungen
    
    		string::size_type nick_begin = line.find_first_not_of(" ");	//Das erste Zeichen in der Zeile, das kein Leerzeichen ist, MUSS das erste Zeichen des Nicknames sein...
    		string::size_type nick_end = split - 1;				// ... d.h. der Nickname muss ein Zeichen (deswegen -1) vor dem ":" enden.
    		string::size_type score_begin = split + 1;			//Das erste Zeichen nach dem ":" muss der Anfang des Scores sein.
    		string::size_type score_end = line.size();			//Das Ende des Scores muss die Länge der Zeile sein
    
    		if(score_begin != string::npos)					//Zeile nach Zerteilen NICHT fehlerhaft
    		{
    			string score = line.substr(score_begin, score_end - score_begin); //neue Variable für Übersichtlichkeit; erstes Argument von substr: Substring von WO, zweites Argument: LÄNGE des Substrings.
    
    			name_score[line.substr(nick_begin, nick_end - nick_begin)] = atoi(t.c_str()) //s.o.;
    		}
    	}									
    
    	in.close();								//ifstream wird geschlossen
    }
    

    So kannst du das Ganze nun aufrufen:

    //entweder:
    string player = "Tom";							//Falls du konkrete Namen überprüfen willst
    
    if(name_score.find(player) != name_score.end())				//Wert wurde in der map gefunden
    	cout << "Spieler " << player << " hat " << name_score[player] << " Punkte erreicht." << endl;
    else									//Wert wurde nicht in der map gefunden
    	cout << "Spieler " << player << " ist nicht im Highscore vertreten." << endl;
    
    //oder:
    map<string, int>::iterator it;						//Falls du die Namen aus der Datei lesen willst/musst
    
    cout << "Spieler " << it->first << " hat einen Highscore von " << it->second << " Punkten erreicht." << endl;
    

    Was passiert, wenn ein neuer Highscore erreicht wurde? Wir können ja davon ausgehen, dass es absteigend sortiert wurde, d.h:

    int newhighscore = x	//neuer Highscore?
    string GetPlayerName(); //gibt den Spielername zurück.
    
    for(map<string, int>::iterator it = name_score.begin(); it != name.score.end(); ++it)
    {
    	if(newhighscore > it->second)
    	{
    		name_score.insert(it, pair<string,int>(GetPlayerName(), x));			//wir fügen an der Stelle it (SpielerName,Highscore) ein, da die Bedingung newhighscore > (irgendein) Highscore erfüllt wurde.
    		break;										//aus dem for-Loop springen, da die Bedingung nur einmal erfüllt sein muss
    	}
    
    }
    
    name_score.erease(name_score.end());								//Es muss nun aber das letzte Element gelöscht werden, da sonst ja ein Element zu viel im Highscore wäre
    

    Nun muss das ganze nur noch in die Datei, nachdem das Spiel beendet wurde:

    vector<string> lines;
    
    for(map<string, int>::iterator it = name_score.begin(); it != name.score.end(); ++it)
    {
    	string t = it->first + ":" + string(it->second);
    	lines.push_back(t);
    }
    
    /* und das muss dann einfach zeilenweise in die Datei. Dafür würde ich einfach die alte Datei überschreiben.*/
    
    ofstream of("scores.txt");
    
    if(of.fail())
    {
    	/* Fehlerbehandlung */
    }
    else
    {
    	for(vector<string>::iterator it = lines.begin(); it != lines.end(); ++it)
    	{
    		of << it* << endl;
    	}
    }
    
    of.close();
    

    Da werden auf jedne Fall ein paar Fehler drin sein, mein Ziel ist es, dass du vielleicht eine Idee aufschnappen kannst, die dir beim Umsetzen hilft - das kurz zu tippen war auf jeden Fall spannender als mich aufs Arbeiten zu konzentrieren (deswegen ist das Ganze auch ungetestet!)

    Viele Grüße 🙂



  • Natürlich muss es so sein:

    int newhighscore = x    //neuer Highscore?
    string GetPlayerName(); //gibt den Spielername zurück.
    
    for(map<string, int>::iterator it = name_score.begin(); it != name.score.end(); ++it)
    {
        if(newhighscore > it->second)
        {
            name_score.insert(it, pair<string,int>(GetPlayerName(), x));            //wir fügen an der Stelle it (SpielerName,Highscore) ein, da die Bedingung newhighscore > (irgendein) Highscore erfüllt wurde.
    name_score.erease(name_score.end());                                //Es muss nun aber das letzte Element gelöscht werden, da sonst ja ein Element zu viel im Highscore wäre
            break;                                      //aus dem for-Loop springen, da die Bedingung nur einmal erfüllt sein muss
        }
    
    }
    


  • while(!in.eof())
    

    Ist meistens - wenn nicht sogar immer - falsch. eof sollte man erst abfragen, nachdem man versucht hat etwas zu lesen.



  • in.eof schrieb:

    while(!in.eof())
    

    Ist meistens - wenn nicht sogar immer - falsch. eof sollte man erst abfragen, nachdem man versucht hat etwas zu lesen.

    :p (Bitte nicht nachahmen)

    int main()
    {
    	string test = "1 2 3 4 5 6 7 8 9";
    	istringstream file( test );
    
    	while( !file.eof() )
    	{
    		int i;
    		file >> i;
    		cout << i << ' ';
    	}
    }
    

    Aber ja, du hast Recht. eof ist nicht dazu da, um zu pürfen, ob die vorangegangene Leseoperation erfolgreich war.



  • in.eof schrieb:

    Ist meistens - wenn nicht sogar immer - falsch. eof sollte man erst abfragen, nachdem man versucht hat etwas zu lesen.

    Daher ist die idiomatische Verwendung der C++-IOStreams auch so konzipiert, dass der Konvertierungsoperator !fail() zurückgibt und nicht good() . Erst wenn dann das EOF-Flag true wird, wird das fail -Flag gesetzt und die Abbruchbedingung wird wahr.

    Dazu prüft eof() nur auf das Ende, aber nicht auf Fehler im Stream. ⚠

    #include <iostream>
    #include <iomanip>
    #include <sstream>
    
    int main()
    {
        std::istringstream stream("abcdef");
    
        int a;
        stream >> a;
    
        std::cout << std::boolalpha << stream.eof();
    }
    

    Edit: out war schneller. Bastard. 😃



  • Um das richtig zu stellen - der Schlüssel dazu ist std::sentry .
    Der Konstruktor von std::sentry macht folgendes:

    [istream::sentry]/2 schrieb:

    If is.good() is false, calls is.setstate(failbit).

    Falls also schon vor der Stream-Operation das EOF-Flag gesetzt war, wird failbit gesetzt. Das wäre Variante 1.
    Variante 2 wäre:

    [istream::sentry]/2 schrieb:

    If is.rdbuf()->sbumpc() or is.rdbuf()->sgetc() returns traits::eof() , the function calls setstate(failbit | eofbit) [...]

    Falls also bei der Leseoperation keine weiteren Zeichen verfügbar sind, wird auch das failbit gesetzt, und die Abbruchbedingung wird wahr.

    Also prüft man in Wirklichkeit natürlich auch auf EOF, wenn man den Konvertierungsoperator von std::ios zum Prüfen nutzt.



  • Sone schrieb:

    dass der Konvertierungsoperator !fail() zurückgibt und nicht good() . Erst wenn dann das EOF-Flag true wird, wird das fail -Flag gesetzt und die Abbruchbedingung wird wahr.

    Das stimmt so aber auch nicht. Wenn das eofbit gesetzt ist, muss das failbit nicht zwangsläufig auch gesetzt sein. Die sind schon unabhängig von einander.



  • out schrieb:

    Sone schrieb:

    dass der Konvertierungsoperator !fail() zurückgibt und nicht good() . Erst wenn dann das EOF-Flag true wird, wird das fail -Flag gesetzt und die Abbruchbedingung wird wahr.

    Das stimmt so aber auch nicht. Wenn das eofbit gesetzt ist, muss das failbit nicht zwangsläufig auch gesetzt sein. Die sind schon unabhängig von einander.

    Das meinte ich ja auch gar nicht. Ich meinte, wenn schon vor einer Extraktion o.ä. das eofbit gesetzt ist, wird auch das failbit gesetzt. Natürlich sind die Unabhängig, das hab ich doch durch mein Beispiel gezeigt 😕



  • Sone schrieb:

    out schrieb:

    Sone schrieb:

    dass der Konvertierungsoperator !fail() zurückgibt und nicht good() . Erst wenn dann das EOF-Flag true wird, wird das fail -Flag gesetzt und die Abbruchbedingung wird wahr.

    Das stimmt so aber auch nicht. Wenn das eofbit gesetzt ist, muss das failbit nicht zwangsläufig auch gesetzt sein. Die sind schon unabhängig von einander.

    Das meinte ich ja auch gar nicht. Ich meinte, wenn schon vor einer Extraktion o.ä. das eofbit gesetzt ist, wird auch das failbit gesetzt. Natürlich sind die Unabhängig, das hab ich doch durch mein Beispiel gezeigt 😕

    :p Und jetzt noch die Preisfrage: In welcher Situation werden immer beide gesetzt?



  • Vielen Dank 🙂


Anmelden zum Antworten