Loop?
-
Hoi,
ich habe wieder ein bisschen mehr Zeit mich C/C++ zu widmen. Zur Zeit bin ich an einem meiner "Lieblingsbeispiele" dran: Black Jack.
Ich hatte schon vor einiger Zeit mein eigenes Black Jack geschrieben - mit billigster AI und so Kleinigkeiten. Der Unterschied: Das war VB.Aber egal; diese ganzen Lustigkeiten (abgesehen vom Array-Management, dass mir derzeit noch etwas schleierhaft erscheint) sind nicht meine Sorge - sondern die Tatsache, dass Programm in einer Schleife laufen zu lassen.
Denkansatz war, eine globale Variable "bool game" zu verwenden. Via while wird dann einfach der typische "Karte/Ende"-Prozess solang durchgezogen, bis game = false. Das (eigentlich völlig logische) Problem: Das Programm will unendliche Karte/Ende-Prozesse durchführen, da "game" ja zu Beginn true ist. Klartext: Sobald ich das Programm ausführe, bekomme ich eine Endlosschleife mit "Karte / Ende?"-Ausgaben.
So während dem Schreiben jetzt ist mir auch noch folgender Gedanke gekommen: Könnte ich nicht ans Ende der while-Schleife game auf false setzen, und falls nicht "Ende" sondern "Karte" gewählt wird, wieder auf true? Somit würde ich die Endlosschleife doch verhindern? Der Gedankengang wirkt aber gerade zu... "die Lösung war zu einfach!".

Ach, und wenn ich schon hier bin noch eine kleine Frage bzgl. Arrays:
Ich hab mir aus Spaß eine Funktion geschrieben die die Farbe einer Karte "generiert" (per Zufall). Hier kommt dann das oben erwähnte Array-Management-Unverständnis ins Spiel;// just to make everything a little bit... cooler! string symbol() { srand( time( NULL ) ); string symbol; int rndv = rand() % 4 + 1; switch(rndv) { case 1: symbol = "Herz"; break; case 2: symbol = "Karo"; break; case 3: symbol = "Kreuz"; break; case 4: symbol = "Pik"; break; } return symbol; }Mit Switch ja ziemlich einfach. Ich hatte zuvor probiert, einen Array namens symbols zu basteln, und dann einfach return symbols[ rndv ] - geht leider nit. Nur... warum? :<
Schöne Grüße, Yada
Nachtrag: Okay, scheinbar war der "die Lösung scheint so einfach zu sein"-Gedankengang richtig. Zumindest ist die Endlosschleife beendet.
Problem jetzt ist, dass nach der ersten Karte das Programm beendet wird.
Nachtrag 2: Okay, Problem behoben. If regelt!
if( action != "c") { game = false; }
-
Zur Schleife kann ich ohne Code nicht viel sagen. Was das Array angeht, vermute ich, dass du auf einen Speicherbereich außerhalb des Arrays zugegriffen hast, wenn rndv == 4 wurde. Array-Indices sind in C++ anders als in VB generell 0-basiert.
Eine Auswahl, wie du sie vorhast, ginge am einfachsten so:
char const *random_symbol() { static char const *const symbols[] = { "Kreuz", "Pik", "Herz", "Karo" }; return symbols[rand() % 4]; }Natürlich kannst du das auch direkt aus der Funktion in einen std::string quetschen, aber ich würde das aus Performancegründen nur bei Bedarf tun.
Übrigens willst du den Zufallszahlengenerator nicht bei jedem Funktionsaufruf neu seeden - srand(time(0)); sollte nur einmal zum Programmanfang ausgeführt werden.
-
Ah, vielen Dank erstmal.
Zu dem angeführten Quellcode hab' ich jedoch eine Frage, die mich brennend interessiert: wofür ist eigentlich der * ?
-
In diesem Fall ist es Teil einer Zeigerdeklaration.
static char const *const symbols[]deklariert ein Array symbols von konstanten Zeigern auf Konstanten des Typs char (Buchstabe). Die Zeiger zeigen in diesem Fall auf den Anfang der String-Konstanten "Kreuz" bis "Karo".
Es handelt sich dabei um eine sehr alte Form der Stringbehandlung, die in C++ von C übernommen wurde. Flach im Speicher liegt das so:
| K | r | e | u | z | \0 | -------------------------- ^ | Zeiger zeigt da hinstd::string hat einen Konstruktor, der so was akzeptiert, also kann man es bei Bedarf leicht in einen solchen pressen. Der Vorteil dieser Vorgehensweise ist, dass man Laufzeit spart, wenn man die Fähigkeiten eines std::string nicht benötigt.
Alternativ kannst du auch folgendes machen:
std::string const &symbol() { static std::string const symbols[] = { "Kreuz", "Pik", "Herz", "Karo" }; return symbols[rand() % 4]; }Das ist einfacher handzuhaben, aber halt nicht ganz so fix (wobei der Unterschied jetzt nicht riesengroß ist).
-
Zu der Schleife: Warum nicht game auf false setzen, wenn Ende gewählt wurde?
-
Seldon, vielen Dank für die ausführliche Erklärung!
Und SeppJ:
Sobald die Runden gestartet sind, soll der Punktestand nach jeder Aktion (sprich entweder c für Karte oder e für Ende) angezeigt werden. Wenn Ende gewählt wird, wird game auf false gesetzt; jedoch auch am Ende der Schleife falls c gewählt wurde - um nicht in einer Endlosschleife auszuarten.
Eine Funktion ermittelt dann jede Runde ob weder Spieler noch Dealer über 21 haben oder kein Geld mehr zum Setzen haben.Vielleicht hab ich heut Nacht etwas zu kompliziert gedacht. Vielleicht aber auch nicht - ich werd mich heute wieder ransetzen! :>
-
Irre ich mich oder ist die Kartenfarbe völlig irrelevant für Black Jack?
#include <algorithm> #include <iostream> #include <vector> #include <string> std::string whichCard(int card) { std::string result; switch (card/13) { case 0: result.append("Karo "); break; case 1: result.append("Herz "); break; case 2: result.append("Pik "); break; default: result.append("Kreuz "); } switch (card%13) { case 0: result.append(" 2 ");break; case 1: result.append(" 3 ");break; case 2: result.append(" 4 ");break; case 3: result.append(" 5 ");break; case 4: result.append(" 6 ");break; case 5: result.append(" 7 ");break; case 6: result.append(" 8 ");break; case 7: result.append(" 9 ");break; case 8: result.append(" 10 ");break; case 9: result.append("Bube ");break; case 10: result.append("Dame ");break; case 11: result.append("King ");break; case 12: result.append("Ace ");break; } return result; } bool game(std::vector<int> &sledge) { // game logic ... use back() and pop_back() to deal a card // deal first cards, then enter your game until player decides to stay or busts the game // return true until player wants to leave for (int c(0); c<sledge.size();++c) std::cout << whichCard(sledge[c]) << std::endl; return false; } int main(int argc, char *argv[]) { // some randomness srand ( unsigned ( time (NULL) ) ); // create a deck of cards std::vector<int> cards; cards.reserve(52); // fill with cardsvalues for (int col(0);col<4;++col) for (int card(0);card<13;++card) { cards.push_back(col*13+card); // [1...51] } bool theEnd=false; while (!theEnd) { // create a card sledge std::vector<int> sledge; sledge.reserve(52*6); for (int decks(0);decks<6;++decks) sledge.insert(sledge.end(),cards.begin(),cards.end()); // shuffle sledge random_shuffle ( sledge.begin(), sledge.end() ); // the loop bool play = true; while (play && sledge.size() > (52)) // if only one deck left, leave game { play = game(sledge); if (!play) { theEnd=true; } } } }
-
Kartenfarbe ist völlig irrelevant, richtig. Ist halt nur "just to make everything a little bit... cooler!". Es kann auf jedenfall nicht schaden, beim Lernen einer Programmiersprache allerhand Elemente einzubauen - die vielleicht nicht relevant sind, aber lehrreich! :>
Und danke für den Quellcode - werd ich mir mal genauer angucken. Ein paar Dinge sind mir beim Drüberfliegen schon mal unbekannt vorgekommen :<
-
Ich repräsentiere jede Karte eines französischen Blattes durch einen Integer von 0 (Karo 2) bis 12 (Karo As) macht 13 Karten * 4 Farben == Zahlen [0..51]. whichCard() gibt dir den "String" zurück. Die Pack ich in nen Vector der ein "Blatt" darstellt. Dann pack ich 6 davon in einen Schlitten (lt. Wikipedia läuft das wohl so beim heutigen BlackJack). random_shuffle aus <algorithm> kann jeden Container mit randomiteratoren mischen. Danach benutze ich den Schlitten (perReference Übergabe an game(vector<int> &sledge) ) zum Spielen solange noch mindestens 52 Karten rest sind. ... danach wird der Schlitten neu bestückt. mit sledge.back() bekommste die letzte Karte, mit pop_back() erntfernst du die vom Sledge ... damit kann man das austeilen "simulieren".
// so könnte man den Wert einer Karte ermitteln ... int whichValue(int card, bool aceLow=false) { switch (card) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: return (card+2); // 2,3,4,5,6,7,8,9 case 12: if (aceLow) {return 1;} return 11; } return 10; // 10,Bube,Dame,König }Je nachdem wie anspruchsvoll dein BlackJack werden wird, kanns schwierig werden alle Karten des Spielers zu überprüfen ... wenn du SPLIT erlaubst würde ich jedem Spieler einen
std::vector<std::vector<int> > ownedCardsmitgeben, sowie einenstd::vector<int> moneyOnCards. Wenn du dann überprüfen willst wie viel Punkte eine Hand hast gehste über alle ownedCards, summierst für jeden inneren vector die Kartenwerte (zweimal falls er ein Ass drin hat und >21 ist ... beim zweiten mal whichValue(...,true). Wenn er bustet entfernste diese Karten aus ownedCards und den Einsatz aus moneyOnCards der den selben index hat wie das gebustete Blatt ... Noch sinniger im Rahmen der Lernerfahrung wäre aber wohl eine vernünftige Kapselung in Klassen.
-
Hammer Erklärung, danke.
Was mir aber noch nicht so ganz klar ist:
- Wofür ist beivector<int>, das <int>?
- Ganz blick ich noch nicht was dasstd::string whichCard(int card)std da sucht? Scheint ja wohl ne Klasse zu sein? Nur was macht sie?
- Istfor (int col(0);col<4;++col)das selbe wiefor (int col = 0 [...]?Btw.: Die Idee mit dem (card + 2) gefällt mir! Irgendwie habe ich mir dafür eine Min/Max-Funktion geschrieben!
Wegen der Klassen: Stimmt schon. Nur bin ich erst seit gestern wieder bei C++ - daher wieder mal prinzipiell in Syntax und Co. einarbeiten.
Ob sich die Erfahrung mit PHP seit 5 Jahren in der Hinsicht auch bezahlt macht? Zwar ist PHP in keinster Weiße mit C++ zu vergleichen, aber die Anlehnung von PHP an C++ ist deutlich erkennbar - und das Prinzip der Klassen sollte doch ähnlich sein? :>
-
Das sind Klassen aus dem Namespace std::
http://www.cplusplus.com/reference/string/string/
http://www.cplusplus.com/reference/stl/vector/Vector ist ein generischer Container ... mein vector ist ein Container für int's (sozusagen ein spezielle Ausprägung des generischen Containers). Der Vorteil der STL Container ist einfach, das die die ganze Speicherverwaltung vom Benutzer kapseln ... wesentlich einfacher und sicherer zu nutzen als rohe Arrays
int cards[52];oder sogar noch selbst allociertes Zeug (new/delete).std::string whichCard(int card) { ... }ist ne Methode: Rückgabetyp, Name(Übergabeparameter) { was sie tut } ... im Wesentlichen gibt die dir den String zurück
Statt jedesmal
std::vector<int>; std::cout; std::stringzu schreiben kannst du das auch anders machen: http://www.cplusplus.com/doc/tutorial/namespaces/ aber in .h's bitte nicht
Das mi for (int i(0) ..) ist das selbe ... kann sein das man so einen Copyconstruktor spart .. aber ich glaube das wird eh rausoptimiert. Bin grad zu faul zu deassemblieren.
-
Vielen Dank!

-
Eieiei, das sind wirklich grundlegendste Grundlagen, man merkt, dass du erst seit gestern dabei bist. Die zwei wichtigen Stichworte: Templates, Namespaces
Das <int> bei vector<int> ist ein Templateparameter. vector ist keine vollständige Klasse für ein dynamisches Array, sondern nur die Vorlage (=Template) für eine solche Klasse. Das heißt die ganzen Algorithmen sind schon da, aber sie wissen noch nicht von welchem Typ das dynamische Array ist. Denn die verwaltung eines dyamischen Arrays gefüllt mit ints funktioniert genauso wie eines gefüllt mit floats, oder std::strings oder mit MeinerEigenenKlasse. Der Templateparameter sagt dem Compiler von welcher Sorte der vector denn nun tatsächlich sein soll. Der Setzt dann quasi diesen Typen in die ganzen Algorithmen ein und macht aus der Klassenvorlage vector eine konkrete Klasse namens vector<int>. Man kann natürlich die Vorlage auch mehrmals für verschiedenen Typen benutzen.
das std:: bei std::string ist die Angabe für den Namespace (Namensraum) in den string zu finden ist. Namensräume dienen dazu Namenskollisionen zu vermeiden. Beispielsweise liegen alle Klassen und Funktionen aus der C++-Standardbibliothek im Namensraum std. Auf diese Weise kannst du selber Klassen, Funktion oder Variablen mit dem Namen string benutzen, ohne dass es zu Konflikten mit der Klasse aus der Standardbibliothek kommt. Am besten packst du deine eigene Klasse string natürlich auch noch in einen extra Namensraum (zum Beispiel Yada::string). So kommt es dann auch nicht zu Konflikten, wenn du deinen Code mal mit dem Code von anderen Entwicklern mischen musst, die eventuell selber den Bezeichner string irgendwo benutzt haben.
edit: Oh, zu langsam. das kommt davon wenn man so viel schreibt.
-
padreigh schrieb:
Das mi for (int i(0) ..) ist das selbe ... kann sein das man so einen Copyconstruktor spart .. aber ich glaube das wird eh rausoptimiert. Bin grad zu faul zu deassemblieren.
Es ist 100%ig das selbe.
Hat auch nichts mit Optimierungen zu tun.
int i = 3;
Ruft niemals einen Zuweisungsoperator oder gar Copy-CTor auf.bb