Zahlenratespiel/ Ständige Probleme mit der Inkrementierung
-
Hallo zusammen,
arbeite momentan mit dem Buch "Einführung in die Programmierung mit C++" von B. Stroustrup und befinde mich auf dem Basisniveau von C++.
Ich möchte das Spiel "Bulls and Cows" programmieren; habe dabei folgendes Problem:
Ich habe einen Vector angelegt, der 4 Zufallszahlen generieren sollcout << endl << endl; int zahl_rundung; cout << " Geben Sie eine beliebige Zahl n als Argument ein: "; cin>>zahl_rundung; srand(zahl_rundung); // Erstelle 4 Zufallszahlen vector<int> vergleich(4); vergleich [0] = randint(10); // Einser vergleich [1] = randint(10); // Zehner vergleich [2] = randint(10); // Hunderter vergleich [3] = randint(10); // Tausenderwobei randint(10) in der von Stroustrup erstellten Headerdatei definiert ist.
Im nächsten Abschnitt bitte ich den Spieler, eine vierstellige Zahl einzugeben; diese teile ich dann mit Hilfe gebastelter Funktionen in vier Int-Werte auf:
#include "../../std_lib_facilities.h" int zerlegen_tausender(int eingabe) { int tausender; tausender = eingabe/1000; return tausender; } int zerlegen_hunderter(int eingabe) { int hunderter; hunderter = (eingabe-(zerlegen_tausender(eingabe)*1000))/100; return hunderter; } int zerlegen_zehner(int eingabe) { int zehner; zehner = (eingabe-(zerlegen_tausender(eingabe)*1000)-(zerlegen_hunderter(eingabe)*100))/10; return zehner; } int zerlegen_einser(int eingabe) { int einser; einser = (eingabe-(zerlegen_tausender(eingabe)*1000)-(zerlegen_hunderter(eingabe)*100)-(zerlegen_zehner(eingabe)*10)); return einser; }Das erste Ziel besteht anschließend darin zu prüfen, ob nun eine Zahl an der richtigen Position geraten wurde (das ergäbe dann einen "Bull"):
do { cout << " Erraten Sie die gesuchte Zahl (4-stellig)! - : "; cin >> users_eingabe; int count_bulls = 0; if(zerlegen_einser(users_eingabe)==vergleich[0]) ++count_bulls; if(zerlegen_zehner(users_eingabe)==vergleich[1]) ++count_bulls; if(zerlegen_hunderter(users_eingabe)==vergleich[2]) ++count_bulls; if(zerlegen_tausender(users_eingabe)==vergleich[3]) ++count_bulls; // Gibt die Anzahl an "Bullen" aus cout << endl << " " << count_bulls << " Bulle(n).\n"; count_bulls = 0; cout << endl; // Hat der Spieler richtig getippt (4 Bullen) ? if(users_eingabe==game_solution) { cout << endl << endl; cout << " Herzlichen Glueckwunsch! Sie haben richtig geraten! Sie haben gewonnen!\n\n"; keep_window_open(); return 0; } } while (users_eingabe!=game_solution);Lange Rede, kurzer Sinn: Ich habe schon seit -TAGEN- (
) ausprobiert, WIE ich diesen++count_bulls;erfolgreich inkrementieren kann. Ich möchte für jede richtig geratene Zahl an der richtigen Position den Zähler um Eins erhöhen, bei mir kamen aber bisher immer nur fehlerhafte Ausgaben (z.B. wurde der counter nur um 1 oder 2 erhöht, obwohl drei richtige Zahlen an der korrekten Position waren). Wer kann mir möglichst einfach (verständlich für den Anfänger!) erklären, wie ich dieses (mit Sicherheit triviale) Problem lösen kann?
Bin für Hilfe sehr dankbar!
-
Ich kann das Problem nicht nachvollziehen. Wenn ich den Rest des Programms ergänze, funktioniert alles korrekt. Der Fehler muss irgendwo anders liegen. Siehe dritter Link in meiner Signatur.
-
Hallo, vielen Dank erstmal für die Antwort.
Damit das Problem ersichtlicher wird, poste ich hier doch sicherheitshalber meinen kompletten Quelltext:#include "../../std_lib_facilities.h" // Diese Funktionen zerlegen die vom User eingegebene 4-stellige Zahl // in jeweils vier Integer-Werte int zerlegen_tausender(int eingabe) { int tausender; tausender = eingabe/1000; return tausender; } int zerlegen_hunderter(int eingabe) { int hunderter; hunderter = (eingabe-(zerlegen_tausender(eingabe)*1000))/100; return hunderter; } int zerlegen_zehner(int eingabe) { int zehner; zehner = (eingabe-(zerlegen_tausender(eingabe)*1000)-(zerlegen_hunderter(eingabe)*100))/10; return zehner; } int zerlegen_einser(int eingabe) { int einser; einser = (eingabe-(zerlegen_tausender(eingabe)*1000)-(zerlegen_hunderter(eingabe)*100)-(zerlegen_zehner(eingabe)*10)); return einser; } // Diese Funktion setzt aus den vier Zufallszahlen eine 4-stellige Zahl int setze_zufallszahl_vierstellig(int ei, int ze, int hu, int ta) { int erg_vierstellig; erg_vierstellig = ((ta+1000)+(hu*100)+(ze*10)+ei); return erg_vierstellig; } // Klasse für Ausnahmebehandlung bei fehlerhaften Eingaben class bad_eingabe {}; // 1 Bulle = Zahl richtig erraten UND an der richtigen Position // 1 Kuh = Zahl richtig erraten, aber NICHT an der richtigen Position int main() try{ cout << endl << endl; cout << " xxxxxxxxxxxxxxxxxx Play a game: 'Bulls and Cows' xxxxxxxxxxxxxxxxxx\n"; int users_eingabe = 0; cout << endl << endl; int zahl_rundung; cout << " Geben Sie eine beliebige Zahl n als Argument ein: "; cin>>zahl_rundung; srand(zahl_rundung); // Erstellt versch. Folgen von Zufallszahlen // Erstelle 4 Zufallszahlen vector<int> vergleich(4); vergleich [0] = randint(10); // Einser vergleich [1] = randint(10); // Zehner vergleich [2] = randint(10); // Hunderter vergleich [3] = randint(10); // Tausender // Nur zu Testzwecken; gibt die Lösung (Zufallszahl) auf dem Bildschirm aus int game_solution; game_solution = setze_zufallszahl_vierstellig(vergleich[0], vergleich[1], vergleich[2], vergleich[3]); cout << " Loesungstest: " << game_solution; cout << endl << endl; do { cout << " Erraten Sie die gesuchte Zahl (4-stellig)! - : "; cin >> users_eingabe; if(!cin) throw bad_eingabe(); int count_bulls = 0; if(zerlegen_einser(users_eingabe)==vergleich[0]) ++count_bulls; if(zerlegen_zehner(users_eingabe)==vergleich[1]) ++count_bulls; if(zerlegen_hunderter(users_eingabe)==vergleich[2]) ++count_bulls; if(zerlegen_tausender(users_eingabe)==vergleich[3]) ++count_bulls; // Gibt die Anzahl an "Bullen" aus cout << endl << " " << count_bulls << " Bulle(n).\n"; count_bulls = 0; cout << endl; // Hat der Spieler richtig getippt (4 Bullen) ? if(users_eingabe==game_solution) { cout << endl << endl; cout << " Herzlichen Glueckwunsch! Sie haben richtig geraten! Sie haben gewonnen!\n\n"; keep_window_open(); return 0; } } while (users_eingabe!=game_solution); cout << endl << endl; keep_window_open(); return 0; } //Ausnahmebehandlungen catch(exception& e) { cerr << " ERROR: " << e.what() << "\n"; keep_window_open(); return -1; } catch(bad_eingabe) { cerr << " Geben Sie bitte eine V I E R S T E L L I G E Zahl ein!\n\n"; keep_window_open(); } catch(...) { cerr << " Unbekannte Ausnahme!\n"; keep_window_open(); return -2; }Gebe icht jetzt als Argument n (für srand() ) z.B. 87 ein, dann würde die Lösung für dieses (halbe) Spiel 1763 lauten.
Wenn ich jetzt diese Lösung mit der Zahl 1722 "errate", dann bekomme ich 3 Bullen, was aber falsch wäre, da nur die 1 und die 7 mit der Lösung übereinstimmen (richtige Position der beiden Zahlen) - es dürfen also nur 2 Bullen ausgegeben werden.
-
erg_vierstellig = ((ta+1000)+(hu*100)+(ze*10)+ei);Guck nochmal genau hin

Im Prinzip kannst du dir aber game_solution mit allem was dazu gehört (unter anderem setze_zufallszahl_vierstellig mit dem gezeigten Fehler) auch sparen. An keiner Stelle musst du mit der Lösung rechnen, es handelt sich bloß um eine Folge von Ziffern. Guck mal, wie ich das in dem Code gemacht habe, den ich nach deinem ersten Beitrag zu einem fertigen Programm ergänzt habe.
-
Guck nochmal genau hin
Oh mein Gott, da ist mir ja wirklich ein peinlicher Fehler unterlaufen - herzlichen Dank für's Korrekturlesen, ohne Deine Hilfe hätte ich sehr wahrscheinlich vor lauter Bäumen den Wald nicht mehr gesehen!

Du hast recht, das "erg_vierstellig" kann ich eigentlich weglassen, das erhöht nur das Risiko, fahrlässige Fehler einzubauen
-
Übrigens, wie ich das so schnell rausgefunden habe*: Gute alte Debugausgaben. Einfach mal alle verdächtigen Variablen (das waren hier nur
vergleichundgame_solution) anzeigen lassen und schon sieht man den Fehler. Ein richtiger Debugger hätte es natürlich auch getan, da er ebenfalls die Werte aller Variablen anzeigen kann. Solltest du dir bei Gelegenheit mal ansehen, wie man mit Debuggern umgeht. Aber vorerst reichen auch Debugausgaben.*: Da dies ein Forum ist, kannst du nicht sehen, wie lange ich gebraucht habe, aber es war nicht lange.
-
Solltest du dir bei Gelegenheit mal ansehen, wie man mit Debuggern umgeht. Aber vorerst reichen auch Debugausgaben.
Jo, das werde ich auf jeden Fall machen; soweit ich weiß kommt in meinem Lehrbuch auch ein Kapitel vor, wo die "Debugging-Geschichte" ausführlich angesprochen wird. Ich kämpfe mich ehrgeizig durch!

Jetzt wäre mein Zahlenratespiel fast fertig, nur gibt es da noch ein Problem: Ich möchte einen kleinen Algorithmus erstellen, der die Anzahl der "Kühe" korrekt herausfiltert, d.h. wenn eine Zahl richtig erraten wurde, jedoch NICHT an der richtigen Position der vierstelligen Zahl steht. Mein vorläufiger Code sieht momentan so aus:// Einser if(vergleich[0] != zerlegen_einser(users_eingabe) && vergleich[0] == zerlegen_zehner(users_eingabe) || vergleich[0] == zerlegen_hunderter(users_eingabe) || vergleich[0] == zerlegen_tausender(users_eingabe)) { ++count_cows; } // Zehner if(vergleich[1] != zerlegen_zehner(users_eingabe) && vergleich[1] == zerlegen_einser(users_eingabe) || vergleich[1] == zerlegen_hunderter(users_eingabe) || vergleich[1] == zerlegen_tausender(users_eingabe)) { ++count_cows; } // Hunderter if(vergleich[2] != zerlegen_hunderter(users_eingabe) && vergleich[2] == zerlegen_einser(users_eingabe) || vergleich[2] == zerlegen_zehner(users_eingabe) || vergleich[2] == zerlegen_tausender(users_eingabe)) { ++count_cows; } // Tausender if(vergleich[3] != zerlegen_tausender(users_eingabe) && vergleich[3] == zerlegen_einser(users_eingabe) || vergleich[3] == zerlegen_zehner(users_eingabe) || vergleich[3] == zerlegen_hunderter(users_eingabe)) { ++count_cows; }Das Ganze ist noch nicht ganz ausgereift: Wenn die zu erratende Zahl = 5448 wäre und man beim Raten "4444" eingibt, dann lautet die Ausgabe mit folgender Ergänzung im Quellcode
// Gibt die Anzahl an "Kuehen" aus cout << " " << count_cows << " Ku(e)h(e).\n"; count_cows = 0; cout << endl;(mit restlichem Quellcode, siehe oben) "2 Bullen. 2 Kuehe.". Das wäre natürlich Unsinn, da in diesem Fall nur 2 "Bullen" vorkommen. Habe schon einiges ausprobiert...
Wie könnte ich das vermeiden und NUR die "Cows" -einmalig- über++count_cows;mitzählen lassen, falls eine davon in der 4-stelligen Zahl vorkommt?
Bin für jede Hilfestellung sehr dankbar!
-
An der Aufgabe habe ich auch mal gesessen.
Was Du brauchst ist ein Konstrukt* wo Du Dir merkst, ob das Ergebnis schon gezählt wurde. Da bietet sich natürlich sowas wie vector<bool> an, der zu dem vector<int> vergleich zugeordnet wird.
Dann wird zusätzlich zu der Abfrage auf Gleichheit gecheckt, ob Du diese Zahl schon gezählt hast.Zu Deinem neuen Programmteil:
Du solltest Dir außerdem mal überlegen, ob Du Dir die ganze Tipperei nicht sparen kannst, wenn Du die Vergleiche in eine Schleife packst und dann auf vergleich[i] prüfst. Ist dann auch übersichtlicher...* Eigentlich brauchst Du sogar zwei, einmal für die zu ratende Zahl und einmal für die Eingabe.
-
vector<bool> ist böse, finde leider den Link nicht zu

Falls man es aber benötigt, kann man diese nicht dereferenzieren.
Wie wäre es da mit
struct cow{ int pos; //position int value; // der Wert } std::vector<cow> cows;und dann einfach über
cows.size()?
-
Meiner bescheidenen Meinung nach solltest Du Dich freimachen von dem Gedanken, dass Du es mit Zahlen zu tun hast.
Es ist ähnlich wie beim Sudoku - dort benutzt man zwar auch Zahlen, aber das hat nix mit Mathe oder so zu tun. Du rechnest nicht mit diesen Zahlen. Die einzelnen Ziffern sind nur Symbole.So könntest Du z.B. den Code als einen string von 4 Zeichen auffassen.
Du wirst sehen, dass Dein Programm erheblich an lesbarkeit gewinnt.Hier z.B. nur mal das einlesen:
#include <algorithm> // std::count #include <iostream> #include <string> typedef char digit; typedef std::string code; bool is_valid(digit c){ const digit digits[] = "0123456789"; // das digit muss in digits enthalten sein return std::count(digits, digits+10, c)==1; } code read_code(){ code c; digit d; // lies digits und fuege diese ein, bis code 4 valide digits enthaelt while(c.size()!=4 && std::cin >> d) if(is_valid(d)) c.push_back(d); return c; } int main(){ code c = read_code(); std::cout << c << '\n'; }
-
Zunächst einmal vielen Dank für eure Ratschläge und Anregungen!
Du solltest Dir außerdem mal überlegen, ob Du Dir die ganze Tipperei nicht sparen kannst, wenn Du die Vergleiche in eine Schleife packst und dann auf vergleich[i] prüfst. Ist dann auch übersichtlicher...
Das ist korrekt, hatte ich auch schon mal so
for(unsigned int n = 0; n < vergleich.size(); ++n) { if(vergleich[n] == user_guess[0] && user_guess[0] != vergleich[0]) ++count_cows; if(vergleich[n] == user_guess[1] && user_guess[1] != vergleich[1]) ++count_cows; if(vergleich[n] == user_guess[2] && user_guess[2] != vergleich[2]) ++count_cows; if(vergleich[n] == user_guess[3] && user_guess[3] != vergleich[3]) ++count_cows; }strukturiert, aber wie gesagt, es läuft noch nicht ganz.
Wie wäre es da mit
struct cow{ int pos; //position int value; // der Wert } std::vector<cow> cows;und dann einfach über
cows.size()?
Das klingt interessant/vielversprechend: Obwohl ich vom Schlüsselwort "struct" schon mal irgendwo etwas "mitbekommen" habe, weiß ich leider noch nicht so ganz genau, wie ich mit dieser Technik - oder besser, mit Deinem Vorschlag - genau arbeiten soll

Könntest Du mir diese Idee etwas genauer erläutern?
-
Voltobal schrieb:
Das ist korrekt, hatte ich auch schon mal so
for(unsigned int n = 0; n < vergleich.size(); ++n) { if(vergleich[n] == user_guess[0] && user_guess[0] != vergleich[0]) ++count_cows; if(vergleich[n] == user_guess[1] && user_guess[1] != vergleich[1]) ++count_cows; if(vergleich[n] == user_guess[2] && user_guess[2] != vergleich[2]) ++count_cows; if(vergleich[n] == user_guess[3] && user_guess[3] != vergleich[3]) ++count_cows; }strukturiert, aber wie gesagt, es läuft noch nicht ganz.
Das sieht schon viel besser aus als die Version mit den
zerlege_*()Funktionen.
Was heißt "es läuft noch nicht ganz"?
-
Die Schleife, die ich erstellt habe, zählt zwar (falls vorhanden) "Cows", jedoch habe ich hier das Problem, dass nicht korrekt gezählt wird: Wenn eine Zahl beispielsweise doppelt in der vom Spieler erratenen 4-stelligen Zahl vorkommt (Bsp.: 4616, hier also die '6'), dann zählt die Schleife doppelt und führt zu unsinnigen Ergebnissen - siehe Zitat von mir:
Das Ganze ist noch nicht ganz ausgereift: Wenn die zu erratende Zahl = 5448 wäre und man beim Raten "4444" eingibt, dann lautet die Ausgabe mit folgender Ergänzung im Quellcode
// Gibt die Anzahl an "Kuehen" aus cout << " " << count_cows << " Ku(e)h(e).\n"; count_cows = 0; cout << endl;(mit restlichem Quellcode, siehe oben) "2 Bullen. 2 Kuehe.". Das wäre natürlich Unsinn, da in diesem Fall nur 2 "Bullen" vorkommen. Habe schon einiges ausprobiert...
Wie könnte ich das vermeiden und NUR die "Cows" -einmalig- über++count_cows;mitzählen lassen, falls eine davon in der 4-stelligen Zahl vorkommt?
Bin für jede Hilfestellung sehr dankbar!Ziel wäre hier, dass sich das Programm merkt, ob ein Ergebnis schon gezählt wurde oder nicht. Die Frage ist nur, WIE ich das anstellen kann - für mich als fortgeschrittener (na ja) Anfänger eine echte Herausforderung...

[Übrigens: Mit Klassen (class, struct), Zeigern und den ganzen Algorithmen aus der Standard Template Library habe ich bisher nocht nicht gearbeitet - steht noch alles an -, falls euch das weiterhilft...]
-
Okay.
Ich gehe also recht in der Annahme, dass diezerlege_*Funktionen Schnee von gestern sind, und ich die nie wieder sehe?
Vielleicht hilft es Dir, wenn Du Dein Bulls and Cows so implementierst, dass jede Zahl nur einmal vorkommt - so steht es auch in der engl. Wikipedia.
Egal, ob Du Die obige Einschränkung noch implementierst oder nicht:
Wenn Du beim Kuehe zaehlen einfach die Bullen mitzaehlst und anschließend wieder abziehst wird es einfacher.
Siehe z.B. Z. 13:typedef std::vector<int> code; int bulls(const code& vergleich, const code& user_guess){ int result = 0; // ... Dein Algorithmus return result; } int cows(const code& vergleich, const code& user_guess){ int result = 0; // .. Dein Algorithmus result-=bulls(vergleich, user_guess); return result; }
-
Hab ich da etwas falsch verstanden? fuer abba als Zahlen und bbbb als Eingabe ist es doch richtig "2 Bullen und 2 Kuehe" zu haben, oder
wie soll das den sonst sein?
-
Hae schrieb:
Hab ich da etwas falsch verstanden?
Dies. Hast du nie Mastermind gespielt?
fuer abba als Zahlen und bbbb als Eingabe ist es doch richtig "2 Bullen und 2 Kuehe" zu haben, oder
wie soll das den sonst sein?2 Bullen, 0 Kühe.
-
Vielleicht hilft es Dir, wenn Du Dein Bulls and Cows so implementierst, dass jede Zahl nur einmal vorkommt - so steht es auch in der engl. Wikipedia.
Das würde die ganze Sache tatsächlich angenehmer gestalten!

Und genau da sind wir an diesem Knackpunkt: Wenn der Spieler jetzt aber eine Zahl in seinem "Guess" zwei- oder dreimal eingibt (Bsp. user_guess: 4383; hier die '3') und im "Lösungsvector" die 3 min. einmal vorkommt, habe ich spätestens ab diesem Zeitpunkt ein ärgerliches Problem: Die Zahl wird über die for-Schleife automatisch doppelt gezählt, genau DAS möchte ich unbedingt verhindern. Das erweist sich viel schwieriger, als ich mir anfangs dachte, oh ja...Ich gehe also recht in der Annahme, dass die zerlege_* Funktionen Schnee von gestern sind, und ich die nie wieder sehe?Hoffentlich rennst Du jetzt nicht weg (
)... Ich hatte es der Übersichtlichkeit halber so geregelt:// Vector mit User-Eingaben vector<int> user_guess(4); user_guess [0] = zerlegen_einser(users_eingabe); user_guess [1] = zerlegen_zehner(users_eingabe); user_guess [2] = zerlegen_hunderter(users_eingabe); user_guess [3] = zerlegen_tausender(users_eingabe);Mit dieser Alternative werden die "Cows" alle brav gezählt, nur eben doppelt oder dreifach - die "Bulls" werden immerhin ignoriert:
for(int i = 0; i < user_guess.size(); ++i) for(int j = 0; j < vergleich.size(); ++j) if(user_guess[i] == vergleich[j]) if(i != j) ++count_cows;
-
Voltobal schrieb:
Mit dieser Alternative werden die "Cows" alle brav gezählt, nur eben doppelt oder dreifach - die "Bulls" werden immerhin ignoriert:
for(int i = 0; i < user_guess.size(); ++i) for(int j = 0; j < vergleich.size(); ++j) if(user_guess[i] == vergleich[j]) if(i != j) ++count_cows;Ich hab's jetzt auch implementiert und hab mir noch ein Hilfskonstrukt
bool is_cow_at[4];gegönnt. Dort speichere ich, ob eine Stelle im Tipp schon als cow gewertet wurde. Ausserdem breche ich die innere Schleife ab, sobald ich eine Kuh gefunden habe - damit ich für eine Zahl im "secret" nicht mehrere cows habe.Ich gebe zu, dass sieht erstmal leichter aus, als es ist. Aber sicher schaffbar, wenn Du Dir über dein händisches Vorgehen im klaren bist.
Du liest Deine Zahl also immer noch über einen int ein und zerteilst die?
Was ist denn, wenn meine Eingabe 0123 ist, oder 0000? IMHO eignet sich der int hier nur bedingt...
-
Irgendwie finde ich, dass du das Ganze viel zu kompliziert angehst.
Mit 53 Zeilen Code war die Aufgabe für mich nach 10 Minuten gelöst, ohne Dinge wie vector oder dem Zerlegen von Zahlen auch nur in Erwgägung zu ziehen.Also entweder habe ich die Aufgabenstellung missverstanden oder du musst deinen Kopf ein wenig frei machen und ganz simpel überlegen, wie man das am einfachsten lösen kann

-
Du liest Deine Zahl also immer noch über einen int ein und zerteilst die?
Was ist denn, wenn meine Eingabe 0123 ist, oder 0000? IMHO eignet sich der int hier nur bedingt...Gut, ich glaube Du hast Recht und mich noch ganz nebenbei überzeugt - der Integer scheint für das Lösen dieses Problems offensichtlich nicht das Gelbe vom Ei zu sein.

Es ist wirklich wie beim Sudoku; vermutlich ist man hier am besten mit einem String oder Char gut bedient.
Ich fahre demnächst in den Urlaub; werde mein Laptop dabei haben und Dir versprechen, das ganze Codefragment nochmal bei Gelegenheit komplett neu zu überarbeiten (und ohne Integer oder gar diese zerlege-Funktionen, Ehrenwort!)
Irgendwann Mitte September poste ich das Ganze dann nochmal hier hin (zu diesem Thread). Würde mich freuen, wenn Du bzw. ihr dann mal wieder vorbeischau(s)t!
Viele Grüße und bis dann!