Problem mit zwei Objekten



  • Hallo zusammen,
    ich habe ein kleines Programm aus meinem Lehrbuch. Dieses ist echt sehr simpel, doch plagt mich ein kleiner Fehler. Es geht darum einen Würfelwurf von zwei Spielern (zwei Objekte) zu simulieren. So sieht der Quelltext der main aus (orignial aus meinem Buch übernommen):

    #include <iostream>
    #include "Player.h"
    using namespace std;
    
    int main()
    {
    	Player player1, player2; //zwei Instanzen
    	char yn;
    	do {
    		cout << "\n1.Spieler: "; player1.gamble(); //die Instanzen sind völlig unabhängig voneinandern
    		cout << "\n2.Spieler: "; player2.gamble();
    
    		if(player1.getScore() > player2.getScore()) //zwei VERSCHIEDENE Werte werden verglichen
    			cout << "\nDer 1.Spieler gewinnt." << endl;
    		else if (player1.getScore() < player2.getScore())
    			cout << "\nDer 2.Spieler gewinnt." << endl;
    		else
    			cout << "\nUnentschieden!" << endl;
    
    		cout << "Fortsetzen(j/n):"; 
    		cin >> yn;
    	}
    	while(yn == 'j' || yn == 'J');
    
    	return 0;
    }
    

    Nun ist das Problem, dass immer die gleiche Werte, also "Unentschieden", herauskommt. Für mich sieht das so aus, als ob beide Objekte die gleichen Zufallszahlen zugewiesen werden. Daher hier noch die Unit der Klasse "Player" und die Unit der Klasse "Random", die nur "Player" nutzt.
    Player.cpp

    #include <iostream>
    #include "Player.h"
    #include "Random.h"
    using namespace std;
    
    unsigned int Player::getScore() {return score;}
    
    void Player::gamble()
    {
    	Random dice; //diese Funktion wird zweimal aufgerufen --> von ZWEI VERSCHIEDENEN Objekten
    	dice.init(1,6);
    
    	int i,rn;
    	score = 0;
    	for (i = 0; i < 5; ++i)
    	{
    		rn = dice.random();
    		cout << rn << ", ";
    		score += rn;
    	}
    	cout << "  Summe: " << score << endl;
    }
    

    Random.cpp

    #include <iostream>
    #include <ctime>
    #include "Random.h"
    using namespace std;
    
    void Random::randomSeed()
    {
    	unsigned int seed = (unsigned int)time(NULL);
    	srand(seed);
    }
    
    void Random::init(int i_min, int i_max)
    {
    	max = i_max;
    	min = i_min;
    
    	randomSeed();
    }
    
    int Random::random(void)
    {
    	return min + rand()%(max+1 - min);
    }
    

    Variablen wie score sind in den Headerdateien deklariert, doch habe ich die nicht gepostet, da diese nur die Klassendefinitionen enthalten.

    Kann mir da jemand helfen?

    Vielen Dank
    lg, freakC++



  • Das Problem ist, dass randomSeed() nicht wirklich "random" ist, sondern einfach die Uhrzeit benutzt. Was passiert also, wenn man 2mal schnell hintereinander

    srand(uhrzeit);
    cout << rand() << '\n'
    

    durchführt? Antwort: Das kommt auf die Genauigkeit der Uhrzeit an und ob man 2mal die "gleiche Uhrzeit erwischt".

    Welches Lehrbuch ist das, wenn ich fragen darf?

    Gruß,
    SP



  • Versuch den Seed nur einmal zu setzen.


  • Mod

    Oder verwende unabhängige Zufallsobjekte. Dein Programm benutzt einen merkwürdigen Mischmasch aus globalem Zufallsgenerator und lokalen Zufallsgeneratorobjekten. Kein Wunder dass das komische Ergebnisse liefert, man muss sich schon für eine Art und Weise entscheiden.



  • Hallo,
    das ist die Lösung einer Übungsaufgabe aus dem Buch "C++ Lernen und professionell anwenden".

    Daran kann es natürlich liegen. Ich dachte aber, dass srand(uhrzeit) nur den Zufallsgenerator initialisiert und dass rand() dann die eigentlichen Zufallszahlen liefert. Warum kommt dann ein unterschiedliches Ergebnis raus?

    lg, freakC++

    @SeppJ: Was meinst Du genau mit dem Mischmasch? Ich finde die Idee der Klasse Random nämlich ansich ganz gut...



  • freakC++ schrieb:

    Hallo,
    das ist die Lösung einer Übungsaufgabe aus dem Buch "C++ Lernen und professionell anwenden".

    Breymann? Krass! Hätte nie gedacht, daß er so einen Kot auf die Öffentlichkeit losläßt.



  • Das "Zufall" bei Zufallsgenerator ist ganz schön übertrieben.

    rand() gibt unterschiedliche Werte zurück, weil irgendwo ein "Zustand" gespeichert wird, wahrscheinlich ein globaler int irgendwo in der Standardbiliothek. Dieser Zustand kann von srand gesetzt werden. rand nimmt den Zustand, berechnet aus dem Zustand ein Funktionsergebnis und einen Folgezustand.

    Mach mal folgendes Experiment:

    srand(0);
    for (int k=0; k<4; ++k) cout << ' ' << rand();
    cout << '\n';
    

    Wiederhole dieses Experiment mehrfach. Setze statt der 0 für srand mal etwas anderes ein. Achte auf die Zahlen, die ausgegeben werden.

    Wenn Du jetzt noch dran denkst, dass time() dasselbe Ergebnis liefern kann, wenn man die Funktion schnell hintereinander aufrufen kann, sollte eigentlich alles klar sein.

    Ich stimme SeppJ zu. Es ist ein hässlicher Mischmasch. Alle Random-Objekte teilen sich einen gemeinsamen "Zustand" und beeinflussen sich daher gegenseitig.

    Gruß,
    SP



  • Das ist aber von Ulla Kirch-Prinz und Peter Prinz.

    lg, freakC++



  • Ok, vielen Dank für eure Hinweise. Dann frage ich mich, warum solch eine Lösung in einem doch seriösen Buch vorgestellt wird.

    lg, freakC++



  • freakC++ schrieb:

    @SeppJ: Was meinst Du genau mit dem Mischmasch? Ich finde die Idee der Klasse Random nämlich ansich ganz gut...

    Sie wäre gut, wenn sie nichr rand() benutzen würde.
    So geht der überraschende RandomDestroy-Angriff, wo ich mithilfe eines anderen Objektes Dein Objekt unbrauchbar mache.

    //seeden sei schon repariert. 
    Random rand1;
    rand1.init(0,1000);
    
    {//Angriff aus fremder Funktion
       Random rand;
       rand.init(0,1000);
       int count=0;
       do
          if(rand.random()!=17)//evtl andere zahl nehmen
             count=0;
       while(count<4);//hier hochschrauben, bis es nicht mehr geht
    }
    
    cout<<rand.random();//gar nicht mehr zufällig
    


  • freakC++ schrieb:

    Das ist aber von Ulla Kirch-Prinz und Peter Prinz.

    Achso.
    Das richtige heißt ja auch "C++: Einführung und professionelle Programmierung". Daß der Prinz-Name an das gute Buch erinnert, ist nur Trittbrettfahrerei.

    freakC++ schrieb:

    Dann frage ich mich, warum solch eine Lösung in einem doch seriösen Buch vorgestellt wird.

    Es ist kein seriöses Buch. Keine Ahnung, was euch boons immer wieder dazu bringt, es zu kaufen.



  • Weil es gute Bewertungen hat und ein eigenes Übungsbuch!

    lg, freakC++



  • freakC++ schrieb:

    Weil es gute Bewertungen hat und ein eigenes Übungsbuch!

    Alle Bücher haben gute Bewertungen. Oder wenigstens keine schlechten. Und Programmierbücher kann der Anfänger, der die Bücher liest, noch gar nicht bewerten. Und das eigene Übungsbuch hatte ich neulich in den Flossen, das ist grottig.



  • Was findest Du denn daran grottig. Du als Erfahrender kannst das wahrscheinlich besser sagen. Sonst finde ich, dass all die Übungen genau den Stoff aufgreifen, die im eigentlichen Lehrbuch behandelt worden sind.

    lg, freakC++



  • freakC++ schrieb:

    Was findest Du denn daran grottig.

    Ähm, zum Beispiel das da:

    #include <iostream> 
    #include <ctime> 
    #include "Random.h" 
    using namespace std; 
    
    void Random::randomSeed() 
    { 
        unsigned int seed = (unsigned int)time(NULL); 
        srand(seed); 
    } 
    
    void Random::init(int i_min, int i_max) 
    { 
        max = i_max; 
        min = i_min; 
    
        randomSeed(); 
    } 
    
    int Random::random(void) 
    { 
        return min + rand()%(max+1 - min); 
    }
    


  • was findest du daran denn so extrem grottig?
    Random::randomSeed()
    ist vermutlich geschmackssache

    #include <iostream> ist zugegebenermaßen unnötig
    und
    int Random::random(void) ist auch weder hübsch noch konsistent - noch c++ig^^ aber deshalb gleich grottig? Oo

    bb



  • #include <iostream> 
    //hat hier nichts zu suchen
    
    #include <ctime> 
    
    #include "Random.h" 
    
    using namespace std; 
    
    void Random::randomSeed() 
    { 
        unsigned int seed = (unsigned int)time(NULL); 
    //Kathastrophe I! Mal mindestens static machen und inkrementieren, sonst passiert das Zeit-Auslesen 
    //tausendmal pro Sekunde und das hat dann mit Zufall nichts mehr zu tun. 
    //time(0) wäre auch hübsch
        srand(seed); 
    //KATHASTROPHE II! Hier wird per Random-Klasse ein privater Zustand 
    //vorgetäsucht aber in Wirklichkeit nur ein total verwirrter 
    //Wrapper um srand/rand gebaut. Das führt mit Sicherheit zu Fehlern. 
    //Ein zusätzliches rand() wäre hier angebracht, um die Sekundenhochzählung 
    //zu verschleiern. 
    } 
    
    void Random::init(int i_min, int i_max) 
    //Kathastrophe III, dafür ist der Konstruktor da. 
    { 
        max = i_max; 
        min = i_min; 
    
        randomSeed(); 
    } 
    
    int Random::random(void) 
    //Unnützes void
    { 
        return min + rand()%(max+1 - min); 
    }
    


  • //Kathastrophe I! Mal mindestens static machen und inkrementieren, sonst passiert das Zeit-Auslesen 
    //tausendmal pro Sekunde und das hat dann mit Zufall nichts mehr zu tun. 
    //time(0) wäre auch hübsch
    

    seh ich hier nicht so - man sollte das init natürlich nicht vor jedem rnd() aufruf aufrufen

    //KATHASTROPHE II! Hier wird per Random-Klasse ein privater Zustand 
    //vorgetäsucht aber in Wirklichkeit nur ein total verwirrter 
    //Wrapper um srand/rand gebaut.
    

    find ich nicht all zu schlimm... am anfang kann man, wenn man zufall braucht und oop zeigen will, (imho) nicht viel anderes machen

    //Ein zusätzliches rand() wäre hier angebracht, um die Sekundenhochzählung 
    //zu verschleiern.
    

    wäre nicht schlecht, da hast du recht
    aber an sich ist das auch noch kein fehler...

    void Random::init(int i_min, int i_max) 
    //Kathastrophe III, dafür ist der Konstruktor da. 
    { 
        max = i_max; 
        min = i_min; 
    
        randomSeed(); 
    }
    

    ich nehm mal an, dass man unterschiedliche zufallsspannen braucht und nicht noch den op= erklären will, weil der erst später kommt und zu gleich nicht zeigen will, dass man hier gar keine klasse braucht, weil man immer nur init(), random() aufruft und somit auch ne fkt hätte nehmen können...

    das mit dem void hatten wir schon mal...

    eigtl ist hier "nur" das bsp. (rand/srand) doof gewählt - ansonsten sinds nur kleinere fehler...
    und von einem bsp auf das ganze buch zu folgern ist zumindest fraglich...

    bb



  • unskilled schrieb:

    find ich nicht all zu schlimm... am anfang kann man, wenn man zufall braucht und oop zeigen will, (imho) nicht viel anderes machen

    Objekte zu haben, nur um so zu tun, als würde man OOP nutzen, ist etwas fragwürdig. So tun, weil das Konzept mehrerer Objekte als unterschiedliche Entitäten nicht beachtet wird. Von einer Klasse erwarte ich normalerweise, dass einzelne Instanzen voneinander unabhängig agieren (ungeachtet dessen, dass ich sie natürlich selber verbinden kann oder abhängige Instanzen im Design so vorgesehen sein können).

    Zwei Objekte, die einen gemischten Status haben, bringen hier nicht viel. Da programmiert man lieber prozedural und weiss dafür, dass es sich um ein und dieselbe Sache handelt.

    unskilled schrieb:

    ich nehm mal an, dass man unterschiedliche zufallsspannen braucht und nicht noch den op= erklären will, weil der erst später kommt und zu gleich nicht zeigen will, dass man hier gar keine klasse braucht, weil man immer nur init(), random() aufruft und somit auch ne fkt hätte nehmen können...

    Oben argumentierst du noch mit OOP, und hier soll der Benutzer plötzlich die Initialisierung vergessen dürfen? Konstruktoren sind für mich ein wichtiger Bestandteil der objektorientierten Programmierung und deren Ansatz, korrekte Objektstati sicherzustellen.



  • Ja, klar ists nicht korrekt - aber ich stell es mir relativ trocken vor, erst dann die erste Klasse zu programmieren, bei der man auch mal was sieht, wenn man Konstruktoren usw fertig behandelt hat...

    Wollte ja auch gar nicht sagen, dass das Buch gut ist - aber ist ja auch gut möglich, dass das das einzige fragwürdige bsp war!?

    bb


Log in to reply