Rechenspiel



  • Hi, ich bin neu hier im Forum und auch neu im Programmieren mit C++, bzw. eigentlich im Programmieren selbst.
    Ich habe zum üben mal ein kleines Rechenspiel geschrieben und würde es gerne mal hier posten. Ich erhoffe mir Tipps und Anregungen zu bekommen wie man evtl. die ein oder andere Sache besser lösen kann. Es Programm ist wie ich finde schon sehr Komplex (also für meine Verhältnisse) und bietet schon recht viele Möglichkeiten.
    Vielleicht hilft es sogar dem ein oder anderen Neuling auch weiter.
    So jetzt erstmal der Code und schon mal vielen Dank für jede Art von Kritik usw. 🙂

    #include <iostream>
    #include <string>
    #include <cmath>
    #include <time.h>
    #include <conio.h>
    #include <fstream>
    
    // Globale Variablen
    // Äquivalent zu OOP Membervariablen
    char rechenzeichen;							// Speichert das zufällig ausgewählte Rechenzeichen.
    int punkteZeichen = 0;						// Speichert die Punkte der einzelnen Rechenoperatoren.
    int schwierigkeitsgrad = 100;				// Gibt an wie schwer das Spiel ist: 10 = Leicht, 100 = Mittel, 1000 = Schwer
    int spieldurchlauf = 5;						// Gibt an wie oft gespielt wird, bevor das Spiel zu ende ist.
    
    float rechne(){								// Berechnet zwei Zufallszahlen
    	srand((unsigned) time(NULL));			// Sorgt dafür, das Zufallszahlen erzeugt werden können, indem das Datum mit berücksichtigt wird!
    	float ergebnis;							// Speichert das Ergebnis von $zufallsZahl1 und $zufallsZahl2.
    	int zufallsOperator = rand() % 4 + 1;	// Wählt zufällig die Rechenoperation aus.
    	float zufallsZahl1 = rand() % schwierigkeitsgrad + 1;	// Zahl zwischen 1 und 1000		  
    	float zufallsZahl2 = rand() % schwierigkeitsgrad + 1;	// Zahl zwischen 1 und 1000		  
    
    	switch(zufallsOperator) {
    		case 1: // Plus
    			rechenzeichen = '+';
    			ergebnis = zufallsZahl1 + zufallsZahl2;
    			break;
    		case 2: // Minus
    			rechenzeichen = '-';
    			ergebnis = zufallsZahl1 - zufallsZahl2;
    			break;
    		case 3: // Mal
    			rechenzeichen = '*';
    			ergebnis = zufallsZahl1 * zufallsZahl2;
    			break;
    		case 4: // Geteilt
    			rechenzeichen = '/';
    			ergebnis = static_cast<int>((zufallsZahl1 / zufallsZahl2) * 100);
    			ergebnis = static_cast<float>(ergebnis / 100);
    			break;
    	}
    
    	std::cout << "Das Ergebnis von " << zufallsZahl1 << " " << rechenzeichen << " " << zufallsZahl2 << " ist?" << std::endl;	// Gibt die Rechenaufgabe für den Spieler aus.
    	return ergebnis;							// Gibt das Ergebnis der Berechnung zurück, um es mit den Spieler-Eingaben zu vergleichen.
    }
    
    int rechenzeichenNummer(){											// Gibt den Multiplikator des Rechenzeichens wieder.
    	switch(rechenzeichen) {
    		case '+':
    			return 2;
    			break;
    		case '-':
    			return 2;
    			break;
    		case '*':
    			return 5;
    			break;
    		case '/':
    			return 5;
    			break;
    	}
    }
    
    void schreibeDatei(int neuerScore){
    	std::ofstream file;											// Bereitet Filesystem vor
    	file.open("score.txt", std::ios::out | std::ios::trunc);	// Öffnet eine Datei zum schreiben und löscht jefalls den aktuellen Inhalt.
    	file << neuerScore << std::endl;							// Schreibt den neuen Highscore in die Datei.
    	file.close();												// Schließt den Dateizugriff wieder. !!!WICHTIG!!! MUSS immer geschehen
    }
    
    int leseDatei(){
    	int score = 0;
    	std::ifstream file;							// Bereitet Filesystem vor
    	file.open("score.txt", std::ios::in);		// Öffnet eine Datei zum lesen.
    	if(file.is_open()) {						// Prüft ob die Datei geöffnet wurde, bzw ob sie existiert.
    		file >> score;							// Schreibt den Inhalt der Datei in die $score.
    		file.close();							// Schließt den Dateizugriff wieder. !!!WICHTIG!!! MUSS immer geschehen
    	} else {
    		std::ofstream file;						// Wenn Datei nicht existiert, neu anlegen.
    		file.open("score.txt");
    		file.close();							// Schließt den Dateizugriff wieder. !!!WICHTIG!!! MUSS immer geschehen
    	}
    	return score;
    }
    
    void score(int richtig){						// Berechnet Punktestand
    	int highscore = leseDatei();				// Liest die Punkte aus der Datei aus und speichert sie als Highscore.			
    	int score = (richtig > 0)					// Punkteberechnung
    		? (richtig * 100 / spieldurchlauf + schwierigkeitsgrad) * 3 + punkteZeichen
    		: 0;			
    
    	std::cout << "Du hast " << richtig << " von " << spieldurchlauf << " richtig." << std::endl;
    	std::cout << "Das sind " << score << " Punkte." << std::endl;
    
    	if(score >= highscore) {		// Wenn der Spieler mehr Punkte erreicht hat, als im Highscore gespeichert, hat er den Highscore geknackt.
    		schreibeDatei(score);
    		std::cout << "Du hast den Highscore geknackt!." << std::endl;
    		std::cout << "Der neue Highscore betraegt: " << score << " Punkte." << std::endl << std::endl;
    	} else 							// Ansonsten aktuellen Highscore ausgeben.
    		std::cout << "Der Highscore betraegt: " << highscore << " Punkte." << std::endl << std::endl;
    }
    
    void runGame(){									// Läd das Spiel
    	int richtig = 0;							// Speichert richtige Antworten
    
    	for(int i = 0; i < spieldurchlauf; i++) {	// Läuft solange bis die Max-Anzahl an Runden erreicht ist.
    		float ergebnis = rechne();				// Öffnet die Berechnung
    		float eingabe;
    
    		std::cin >> eingabe;					
    		if(eingabe == ergebnis) {
    			richtig += 1;
    			punkteZeichen += rechenzeichenNummer() * 15;		// Punkteberechnung aufgrund der Schwierigkeit der Rechenoperatoren.
    			std::cout << "Richtig!" << std::endl << std::endl;
    		} else {
    			std::cout << "Falsch!" << std::endl << std::endl;
    		}
    	}
    	score(richtig);
    }
    
    void einstellungen(){
    	int schwer;					// Speichert die Benutzereingabe für den Schwierigkeitsgrad.
    	std::cout << "Waehle den Menuepunkt den du aendern willst!" << std::endl << std::endl;
    	std::cout << "Schwierigkeitsgrad	=> s" << std::endl;
    	std::cout << "Anzahl Runden	        => a" << std::endl;
    
    	char c = _getch();			// Wartet auf die Eingabe vom Benutzer, gibt aber die Eingabe nicht aus.
    	if(c == 's') {				
    		std::cout << "Leicht	=> 1" << std::endl;
    		std::cout << "Mittel	=> 2" << std::endl;
    		std::cout << "Schwer	=> 3" << std::endl << std::endl;
    		std::cin >> schwer;
    		switch(schwer) {
    			case 1:
    				schwierigkeitsgrad = 10;
    				break;
    			case 2:
    				schwierigkeitsgrad = 100;
    				break;
    			case 3:
    				schwierigkeitsgrad = 1000;
    				break;
    			default:
    				schwierigkeitsgrad = 100;
    				break;
    		}
    	} else if(_getch() == 'a'){
    		int runden;					// Speichert die Benutzereingabe für die Rundenanzahl.
    		std::cout << "Waehle wie viele Runden du Spielen willst!" << std::endl;
    		std::cout << "Die Rundenanzahl muss zwischen 5 und 20 Runden betragen!" << std::endl;
    		std::cin >> runden;
    
    		if(runden >= 5 && runden <= 20)
    			spieldurchlauf = runden;
    		else
    			std::cout << "Die eingegebene Rundenanzahl ist ungueltig!" << std::endl;
    	}
    }
    int main(){
    	bool exit = false;
    
    	do {							// Solange $exit false ist, wird das Spiel nicht beendet.
    		std::cout << "Neues Spiel     => n" << std::endl;
    		std::cout << "Einstellungen   => e" << std::endl;
    		std::cout << "Beenden         => b" << std::endl;
    
    		char c = _getch();			// Wartet auf die Eingabe vom Benutzer, gibt aber die Eingabe nicht aus.
    		switch(c) {
    			case 'n':
    				std::cout << std::endl;
    				runGame();			// Startet Spiel
    				break;
    			case 'e':
    				std::cout << std::endl;
    				einstellungen();	// Öffnet Einstellungen
    				break;
    			case 'b':
    				exit = true;		// Beendet Spiel
    				break;
    		}
    	} while(!exit);
    
    	return 0;
    }
    


  • Hi,

    was mir beim Überfliegen auffiel:
    - Keine globalen Variablen benutzen.
    - srand() schreibst du am besten immer in die erste Zeile der main. Dann kannst du sicher sein, dass du den Generator nur 1x initialisiert hast.
    - Wenn dein case ein return hat, ist das break überflüssig, weil die Funktion ja dann fertig ist.
    - 2 case machen ja dasselbe. Kannst du zusammenfassen.
    - Du musst deine Datei nicht schließen, dass passiert automatisch wenn der Destruktor des streams aufgerufen wird. (RAII googeln)
    - Das i in ifstream steht für input. ios::in (put) ist logischerweies überflüssig.

    Da gibts bestimmt noch vieles mehr. 😃



  • Ok, super. habe jetzt die meisten Dinge angepasst. Allerdings gelingt mir das bei den Globalen Variablen nicht. habe ka wie ich es sonst regeln sollte. Ich weiß natürlich, dass man das nicht tun sollte. Ich nutze das in diesem Beispiel für mich so wie die Membervariablen in der OOP. Nur halt nicht mit Getter und Setter Methoden. Man muss es für die erste Übung ja auch nicht ganz übertreiben. 😃
    In diesem Sinne, herzlichen dank für die guten Anregungen 🙂



  • tomy86 schrieb:

    habe ka wie ich es sonst regeln sollte.

    Geeignete Datenstrukturen und Funktionsparameter.



  • Ich bin wie gesagt neu im Gebiet! Ich habs ja nicht umsonst so gemacht, wie es oben zu sehen ist. Deswegen wäre es hilfreich konkreter zu werden. 😉



  • Sorry. Aber dazu fehlt wohl vielen einfach die Motivation bei so einem Schwachsinnsprogramm.



  • tomy86: lass dich mal von so arroganten Kommentaren wie dem von Swordfish nicht runtermachen. Ist schon okay, man muss das ja auch lernen. Einige vergessen vielleicht, wie planlos man so als Anfänger ist.
    Außerdem kam hier auch schon wesentlich schlimmeres an.

    Zu deiner Frage:
    Wie Swordfish schrieb, solltest du Funktionsparameter nehmen. Damit das nicht etwas ausartet, kannst du zusammengehörige Daten gruppieren, mit Klassen oder auch etwas wie std::tupel.

    Sehen wir uns der einfachheit halber mal die Funktion rechenzeichenNummer an. Hier kann man ganz einfach einen Parameter einführen, nämlich das Rechenzeichen und schon ist die Funktion unabhängig von globalen Variablen.
    Natürlich musst du an entsprechender Stelle den korrekten Wert übergeben.

    int rechenzeichenNummer(char rechenzeichen){                                          // Gibt den Multiplikator des Rechenzeichens wieder.
        switch(rechenzeichen) {
            // die breaks kann man sich sparen, da du ja ueber die returns niemals in die anderen cases rutscht
            case '+':
                return 2;
            case '-':
                return 2;
            case '*':
                return 5;
            case '/':
                return 5;
        }
    }
    

    Man könnte die cases oben Zusammenfassen, da wie bereits erwähnt wurde zwei jeweils dasselbe tun. Allerdings passt hier der Wert nicht mit den Werten zusammen, wie die Rechenzeichen erzeugt werden. Ist das so beabsichtigt?



  • Ah ok, ist hier wohl kein Forum was Anfänger freundlich ist!



  • Ach, das würde ich nicht so sagen. Der Ton ist rau, aber hilfreich, wenn man sich beim Stellen der Frage Mühe gibt und über die Antworten entsprechend nachdenkt.
    Vielleicht war hier das Problem, dass du eine sehr allgemeine Frage gestellt hast.



  • Hyde++, ich danke dir für die ausführliche Antwort 🙂
    Also das die Werte unterschiedlich sind liegt daran, dass es sich in der Funktion rechenzeichenNummer um Multiplikatoren handelt. Sie wird in der Funktion runGame aufgerufen und mit 15 multipliziert einfach weil es ja schwerer ist */ als +- zu rechnen. Das wird benötigt für den Highscore.

    Zu den Parametern habe ich mir auch schon Gedanken gemacht, allerdings kommen die Werte ja aus der Funktion rechne und ich brauche die Werte in der Funktion runGame.
    Da müsste ich doch zick aufrufe an zick Stellen machen, damit die Daten so über kommen?



  • Hyde++ schrieb:

    Sehen wir uns der einfachheit halber mal die Funktion rechenzeichenNummer an. [...]

    Ne, das machen wir bitte nicht. Schon einmal darum nicht, weil alleine der Name bullshit ist - er impliziert eine Nummerierung von Rechenzeichen, die nicht gegeben ist - und eine Zuordnung von Operationen zu Multiplikatoren für irgendeinen Score bestenfalls ein Array oder eine map ist und keine Funktion.


  • Mod

    Swordfish schrieb:

    Hyde++ schrieb:

    Sehen wir uns der einfachheit halber mal die Funktion rechenzeichenNummer an. [...]

    Ne, das machen wir bitte nicht. Schon einmal darum nicht, weil alleine der Name bullshit ist - er impliziert eine Nummerierung von Rechenzeichen, die nicht gegeben ist - und eine Zuordnung von Operationen zu Multiplikatoren für irgendeinen Score bestenfalls ein Array oder eine map ist und keine Funktion.

    Zwar wäre rechenzeichenMultiplikator tatsächlich ein besserer Name, aber ein array oder eine map für 4 verschiedene Möglichkeiten vor zu schlagen, ist ja wohl selber bullshit². Am besten noch eine Hashmap, dann können wir die Hashfunktion wieder rechenzeichenNummer nennen:

    int rechenzeichenNummer(char rechenzeichen)
    {
      switch(rechenzeichen)
      {
        case '+': return 1;
        case '-': return 2;
        case '*': return 3;
        case '/': return 4;
      }
      return 0;
    }
    
    int rechenzeichenMultiplikator(char rechenzeichen)
    {
      static const int multiplikatormap[5] = {0, 2, 2, 5, 5};
      return multiplikatormap[rechenzeichenNummer(rechenzeichen)];
    }
    

    Moderne Programmierung!



  • SeppJ schrieb:

    Am besten noch eine Hashmap,

    Mindestens!!



  • SeppJ schrieb:

    int rechenzeichenNummer(char rechenzeichen)
    {
      switch(rechenzeichen)
      {
        case '+': return 1;
        case '-': return 2;
        case '*': return 3;
        case '/': return 4;
      }
      return 0;
    }
    
    int rechenzeichenMultiplikator(char rechenzeichen)
    {
      static const int multiplikatormap[5] = {0, 2, 2, 5, 5};
      return multiplikatormap[rechenzeichenNummer(rechenzeichen)];
    }
    

    Moderne Programmierung!

    Ich verstehe nicht ganz, warum das oben besser sein soll als das:

    int rechenzeichenMultiplikator(){											// Gibt den Multiplikator des Rechenzeichens wieder.
    	switch(rechenzeichen) {
    		case '+':
    		case '-':
    			return 2;
    		case '*':
    		case '/':
    			return 5;
    	}
    }
    

    Das ist doch viel kürzer und bewirkt genau das gleiche? Und sicher ist es doch auch von der Performance viel schneller, da nicht extra wieder noch eine andere Funktion aufgerufen werden muss?


  • Mod

    Wir brauchen wirklich endlich Ironie-Tags für das Forum.


  • Mod

    SeppJ schrieb:

    Wir brauchen wirklich endlich Ironie-Tags für das Forum.

    Ich fürchte dass diese dann leider missbraucht werden und zahllose Threads unlesbar und zweideutig machen würden.


  • Mod

    Arcoth schrieb:

    SeppJ schrieb:

    Wir brauchen wirklich endlich Ironie-Tags für das Forum.

    Ich fürchte dass diese dann leider missbraucht werden und zahllose Threads unlesbar und zweideutig machen würden.

    Dann eben nur verfügbar für verantwortungsvolle, verdiente Personen mit Vorbildfunktion. Wie Moderatoren. [/ironie]



  • *lach*


  • Mod

    Du hast vor einer kleinen Weile mal das Wort "Penis" in einem fraglichen Kontext verwendet. Das nennst du vorbildhaftlich?

    (Hier ist der Link. Irgendein kleinpubertärer Troll hat den Thread zur damaligen Zeit mit dummen und naivem Schwachsinn zugekleistert... ein Vorläuferaccount von Swordfish? 🤡 )

    ~PS: Ich habe nicht einfach vulgäre Wörter in die Forensuche gesteckt um zu schauen wo du gesündigt hast, ich erinnere mich an viele solche Threads leider nur zu gut, da sie in meinem Hirn wie gebrandmarkt herumgeistern.~ 😞



  • Arcoth schrieb:

    Irgendein kleinpubertärer Troll hat den Thread zur damaligen Zeit mit dummen und naivem Schwachsinn zugekleistert... ein Vorläuferaccount von Swordfish? 🤡 )

    Hey ... bleib sportlich ... 😮


Anmelden zum Antworten