Highscore speichern



  • Hi
    Ich habe eins Snake Spiel geschrieben und möchte nun den highscore speichern (nur die besten 10). Ich weia wie man den score in eine datei schreibt (ofstream)aber nicht , wie ich vergleichen soll, ob der score ob der score größer ist als der niedrigste von den 10. Wenn ich versuche, die scores auszulesen gibt das nur scores von 1-9 aus. Kann mir vlt einer von euch helfen? Danke 🙂


  • Mod

    Dann wirst du wohl was falsch gemacht haben. Zeig Code.



  • ifstream datei("score.txt", ios_base::in);
    vector<int> sc;
    int n;
    
    if(!datei)
    {
    	cerr << "Fehler beim oeffnen!";
    }
    
    while(!datei.eof())
    {
     //weis nicht so richtig wie ich das einlesen soll			
    }
    sort(sc.begin(),sc.end());
    
    ...
    
    ofstream dat("score.txt", ios_base::out | ios_base::app);
    
    dat << score;	
    
    	for(int i = 1;  i < sc.size() && i < 10; i++)
    	{
    		cout << i << ": " << sc[i-1] << endl;
    	}
    

  • Mod

    while(!datei.eof())
    {
     //weis nicht so richtig wie ich das einlesen soll         
    }
    

    Schonmal komplett falscher Ansatz. Erst lesen, dann prüfen, dann, bei Erfolg, verarbeiten.
    Hier zum Beispiel:

    for(int score; datei >> score;)
    {
      sc.push_back(score);
    }
    

    So wird erst gelesen ( datei >> score ), dann auf Erfolg geprüft (Rückgabewert von datei >> score ) und dann erst das korrekt gelesene score verarbeitet.

    dat << score;
    

    Ein Trennzeichen wäre gut, sonst bekommst du die Daten hinterher nicht mehr auseinander. Zum Beispiel ein Leerzeichen oder ein Zeilenumbruch.

    Insgesamt passt dein Algorithmus nicht zu deiner Beschreibung. Du hängst den neuen Spielstand einfach hinten dran. Du möchtest doch wohl eher den neuen Spielstand an passender Stelle in die Liste einfügen und dann die komplette Datei mit den besten zehn Punkteständen überschreiben.



  • Danke 🙂 jetzt gehts 🙂
    aber wie sortiere ich den Vector, dass am Anfang die höchste Zahl steht und am ende die niedrigste?





  • stefkowa schrieb:

    mit std::sort siehe: http://en.cppreference.com/w/cpp/algorithm/sort

    ⚠ Ja, aber da muss das Prädikat std::greater<int>() sein.
    ➡

    vector<int> sc;
    // [...]
    std::sort( sc.begin(), sc.end(), std::greater<int>() );
    

    (Ungetestet)



  • 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