[Anfänger] Fragen zur Programmierung mit Klassen
-
Erst wenn alle Charaktere die momentane Aktion der jeweiligen Initiative (z.B. Spieler, Gegner0001 und Gegner0002 haben alle Initiative 5) abgeschlossen haben, wird erst geprüft, ob jemand durch die Aktionen gestorben ist. Dann kommen die Charaktere dran, die Initiative 6 oder höher haben.
In rundenbasierten Spielen würde ich eine Gleichzeitigkeit eher vermeiden wollen, das kann sonst zu bizarren Situationen führen. (Wobei das halt dein Bier ist. :))
Meine Idee wäre so etwas:class Character { int initiative_; public: Character() : initiative_(newRound()) {} void newRound() { initiative_ = irgendwasZufaelliges(); } //friend bool compareInitiative(const Character& a, const Character& b) //{ // return a.initiative_ < b.initiative_; //} }; class Manager { std::vector<Character> characters; public: void addEnemie(Character character) { enemies.push_back(character); } void attack() { for (unsigned int initiative = 1; initiative < 101; ++initiative) { std::vector<std::vector<Character>::iterator> round; for (std::vector<Character>::iterator i = characters.begin(); i != characters.end(); ++i) { if (i->initiative() == initiative) round.push_back(i); } std::random_shuffle(round.begin(), round.end()); for (std::vector<std::vector<Character>::iterator>::const_iterator i = round.begin(); // langsam wirds ironisch.. i != round.end(); ++i) { (*i)->attack(); } } } void newRound() { for (std::vector<Enemy>::iterator i = enemies.begin(); i != enemies.end(); ++i) { i->newRound(); // Jeder Charakter setzt seine eigene Initiative } //std::sort(enemies.begin(), enemies.end(), compareInitiative); } }Ob sort() laufzeittechnisch Sinn macht, kann ja jemand anders ausrechnen, obwohl das bei solche Anwendungen eh völlig egal ist.
Edit:
In dem "original"-Spiel gibt es Gleichzeitigkeit?
Wo genau liest du das, konnte es da nicht finden..
-
cooky451 schrieb:
Vielleicht macht ein std::vector<Character&> round; mehr Sinn.
In einen vector kann man keine Referenzen stecken

-
314159265358979 schrieb:
cooky451 schrieb:
Vielleicht macht ein std::vector<Character&> round; mehr Sinn.
In einen vector kann man keine Referenzen stecken

Hmja, ein resize() dürfte dann schwer fallen.

-
cooky451 schrieb:
In dem "original"-Spiel gibt es Gleichzeitigkeit?

Wo genau liest du das, konnte es da nicht finden..Du hast recht, das steht gar nicht in dem Wiki-Artikel. Aber es gibt Gleichzeitigkeit in AD&D (siehe http://www.ebay.de/itm/270813851609?ssPageName=STRK:MEWAX:IT&_trksid=p3984.m1438.l2649
) - wir reden hier aber auch von einem Pen&Paper-Spiel. Ich meine aber sogar, dass selbst in der bekannten PC-Umsetzung "Baldurs Gate" die Gleichzeitigkeit umgesetzt wird.
Warum auch nicht: Während ich dem fiesen Ork ein Schwert in den Bauch bohre, schlägt er mir noch mit seinem Streitkolben den Schädel ein...

Das werde ich dann auch regelgetreu umsetzen.Ich danke für die vielen Vorschläge. Ich selber habe mir natürlich auch noch weiter Gedanken gemacht. Ich glaube, ich werde es erstmal mit einer einfachen for-Schleife versuchen, die jeden Initiative-Wert durchgeht und dann jeden drankommen lässt.
Ich denke auch, ich werde meine Lösung dann hier posten.
-
Du könntest das Problem der Gleichzeitig damit lösen, dass du alle Charaktere mit gleichem Initiativwert in einen Vektor steckst. Damit hast du mehrere Vektoren, die du dann noch nach ihrem Initiativwert sortieren musst. Glücklicherweise kann man
std::mapgenau dafür verwenden.std::maprealisiert eine Zuordnung von Initiative auf einen Vektor mit Charakteren, die diesen Initiativwert besitzen. Bei dieser Zuordnung spricht man von Schlüssel/Wertepaaren, der Schlüssel ist die Initiative, der Werte ein Vektor von Charakteren.std::maphat das Standardverhalten, dass sie ihre Schlüssel/Wertepaare aufsteigend nach dem Schlüssel sortiert, d.h. beim Durchlaufen derstd::mapwerden Schlüssel/Wertepaare mit dem kleineren Schlüssel zuerst behandelt. Da die Charakterobjekte während des Kampfes verändert werden müssen enthält die Tabelle Zeiger auf die Original Charakterobjekte. Leider verschlechtert das die Lesbarkeit des Quelltextes etwas .Bsp:
Schlüssel (Initiative) | Werte (Vektor von Charakteren und Monstern) -----------------------+-------------------------------------------- 1 | [Monster1, Monster4, Charakter3] 2 | [Monster2] 3 | [Monster5, Charakter1, Charakter2] 4 | [Charakter4] 5 | [Monster6] 6 | [Monster3, Charakter5] -----------------------+--------------------------------------------Um eine Kampfrunde durchzuführen muss diese Tabelle nun von hinten nach vorne abgearbeitet werden, damit die Beteiligten in der Reihenfolge ihrer Initiative handeln können. Charaktere dürfen nur dann angreifen, wenn sie zum Zeitpunkt, an dem sie handeln dürfen, noch handlungsfähig sind. Ein Charakter A mit Initiative 3 könnte ja von Charakter B mit Initiative 5 getötet worden sein, bevor Charakter A an der Reihe war. Dazu wird vor dem Angriff geprüft, ob der aktuelle Charakter überhaupt handlungsfähig ist.
#include <map> #include <vector> using namespace std; typedef vector<Charakter> CharVector; typedef vector<Charakter*> CharPtrVector; typedef map<int,CharPtrvector> InitiativeCharTabelle; bool kann_handeln( const Charakter& Char ) { return Char.Lebenspunkte > 0; } InitiativeCharTabelle erstelle_rangfolge( CharVector& v ) { InitiativeCharTabelle RangFolge; for( CharVector::iterator i = v.begin(); i != v.end(); ++i ) { // Initiativwurf für den aktuellen Charakter int Initiative = (rand() % 6) +1; // oder sonstwie // Alle Charaktere mit gleicher Initiative in einem Vektor ablegen // Die folgende Zeile nutzt eine Eigenheit von std::map aus: // Der Indexoperator[s] liefert das Wertobjekt zum Schlüssel s zurück. // Wenn für den Schlüssel s kein solches Objekt existiert wird ein neues // konstruiert und zurückgegeben. Beim ersten Zugriff RangFolge[s] wird // also ein neues CharPtrVector Objekt erzeugt und zurückgegeben. Damit // wird sichergestellt, dass der Zugriff über den Indexoperator immer // ein gültiges Objekt zurückgibt. // *i dereferenziert den Iterator und liefert damit das Charakterobjekt // zurück. &(*i) ist also die Adresse des Charakterobjekts und wird in // der Tabelle der Rangfolge abgelegt. RangFolge[Initiative].push_back( & (*i) ); } return RangFolge; } void fuehre_kampfrunde_durch( InitiativeCharTabelle& RangFolge ) { // Tabelle von hinten nach vorne durchlaufen, damit Charaktere mit hoher // Initiative zuerst behandelt werden for( InitiativeCharTabelle::reverse_iterator i = RangFolge.rbegin(); i != RangFolge.rend(); ++i ) { // der Iterator einer std::map besteht aus std::pair, in diesem Fall // aus std::pair<const int, CharPtrVector>. // Zur Übersichtlichkeit eine lokale Variable benutzen CharPtrVector& v = i->second; // alle Charaktere des aktuellen Vektors behandeln for( CharPtrVector::iterator c = v.begin(); c != v.end(); ++c ) { // Vektor aller handlungsfähigen Charaktere bilden. Das löst das // Problem, wenn ein Charakter einen anderen Charakter mit gleicher // Initiative tötet. CharPtrVector hfc; if( kann_handeln( **c ) ) { hfc.push_back( *c ); } for( CharPtrVector::iterator hfci = hfc.begin(); hfci != hfc.end(); ++hfci ) { (*hfci)->attackiere( wenAuchImmer ); } } } } int main() { // Vektor mit allen am Kampf beteiligten (Spieler + Monster) vector<Charakter> Kombatanten; // Tabelle mit Vektoren der Charaktere, nach Initiative AUFSTEIGEND sortiert InitiativeCharTabelle RangFolge = erstelle_rangfolge( Kombatanten ); // Kampfrunden durchführen, bis alle die Schnauze voll haben do { fuehre_kampfrunde_durch( RangFolge ); } while( !Schnauze_voll ); }Edit: Logikfehler korrigiert
-
ich habe erstmal angefangen, meinen bisherigen Code an meinen neuen Wissenstand anzupassen. Ich bin aber gleich über einen Fehler gestolpert, weil ich wohl Variablen einer vererbten Klasse mit einem Konstruktor setzen will...
Was kann ich tun? Wer den Kontext hier verfolgt hat, wird nachvollziehen können, dass eine Initialisierung direkt in der Klasse Enemy keine Option ist!Achtung, ich habe einiges aus diesem Quelltext entfernt, weil übersichtlicher!
Die Fehler:\main02.cpp(60): error C2614: 'Enemy': Unzulässige Elementinitialisierung: 'nIniMod' ist weder Basis noch Element \main02.cpp(60): error C2614: 'Enemy': Unzulässige Elementinitialisierung: 'nDamage' ist weder Basis noch Element \main02.cpp(60): error C2614: 'Enemy': Unzulässige Elementinitialisierung: 'nHPnow' ist weder Basis noch Element \main02.cpp(60): error C2614: 'Enemy': Unzulässige Elementinitialisierung: 'nHPmax' ist weder Basis noch Element \main02.cpp(60): error C2614: 'Enemy': Unzulässige Elementinitialisierung: 'nAC' ist weder Basis noch Element \main02.cpp(60): error C2614: 'Enemy': Unzulässige Elementinitialisierung: 'nETW0' ist weder Basis noch Element \main02.cpp(60): error C2614: 'Enemy': Unzulässige Elementinitialisierung: 'strClass' ist weder Basis noch Element \main02.cpp(60): error C2614: 'Enemy': Unzulässige Elementinitialisierung: 'strRace' ist weder Basis noch Element \main02.cpp(60): error C2614: 'Enemy': Unzulässige Elementinitialisierung: 'strName' ist weder Basis noch ElementZeile 60 entspricht in dem folgenden Listing Zeile 36/37!
class Charakter { public: string strName; string strRace; string strClass; int nETW0; int nAC; int nHPmax; int nHPnow; int nDamage; int nInitiative; int nIniMod; string strEnemyName; int nEnemyAC; void fInitiative(); int fAttack(); }; class Player : public Charakter { public: string strPlayerName; }; class Enemy : public Charakter { public: Enemy(string vName, string vRace, string vClass, int vETW0, int vAC, int vHPmax, int vHPnow, int vDamage, int vIniMod); int nXP; }; Enemy::Enemy(string vName, string vRace, string vClass, int vETW0, int vAC, int vHPmax, int vHPnow, int vDamage, int vIniMod) : strName(vName), strRace(vRace), strClass(vClass), nETW0(vETW0), nAC(vAC), nHPmax(vHPmax), nHPnow(vHPnow), nDamage(vDamage), nIniMod(vIniMod) { } int main() { //Zufallszahlengenerator mit Zeit initialisieren srand((unsigned int)time(0)); //Spielercharakter wird deklariert Player Spieler; Spieler.strName = "Arthus"; Spieler.strRace = "Mensch"; Spieler.strClass = "Kaempfer"; Spieler.nETW0 = 14; Spieler.nAC = 4; Spieler.nHPmax = 35; Spieler.nHPnow = 35; Spieler.nDamage = 8; Spieler.nIniMod = 5; //Gegnercharakter wird deklariert Enemy Gegner("Ogercheffe","Oger","Berserker",14,5,23,23,10,5); return 0; }
-
Kleiner Tipp: Die ungarische Notation ist hier nicht gerne gesehen, weil sie keine Vorteile bringt

-
@thuering
Ich habe mal Google bemüht: http://www.dandwiki.com/wiki/SRD:Initiative
Hier steht, dass so lange neu gewürfelt wird, bis eine klare Reihenfolge da ist. Oder habe ich das falsch verstanden?Zu deinem Problem:
class Character { public: enum Race { orc, elf, human, }; enum Profession // class ist immer doof in C++ :) { warrior, mage, ranger, }; private: const Race race_; const Profession profession_; const unsigned int max_life_, damage_, initiative_; unsigned int current_life_; public: Character(Race race, Profession profession, unsigned int max_life, unsigned int damage, unsigned int initiative) : race_(race), profession_(profession), max_life_(max_life), damage_(damage), initiative_(initiative), current_life_(max_life) {} void attack(Character& character) { if (character.current_life_ > this->damage_) character.current_life_ -= this->damage_; else character.current_life_ = 0; } }; class Enemy : public Character { public: Enemy(Race race, Profession profession, unsigned int max_life, unsigned int damage, unsigned int initiative) : Character(race, profession, max_life, damage, initiative) {} };Damit solltest du so eine Grundidee bekommen haben. Du könntest natürlich auch attack() virtuell machen und dann wieder je nach Klasse ableiten etc., der Fantasie sind hier (fast) keine Grenzen gesetzt, versuche ein System aufzubauen, das du nachher leicht verwalten kannst. (Tipp: Wenn du die Basisklasse rein virtuell machst kannst du trotzdem einen std::vector<Character*> (man beachte den Pointer) erstellen.)
-
In einer Initialisierungsliste kannst du nur Elemente initialisieren, die unmittelbar zur Klasse des zu konstruierenden Objekts gehören. In deinem Beispiel versuchst du in Initialisierungsliste der Klasse
EnemyElemente der KlasseCharakterzu initialisieren, was nicht erlaubt ist.
-
DocShoe schrieb:
In einer Initialisierungsliste kannst du nur Elemente initialisieren, die unmittelbar zur Klasse des zu konstruierenden Objekts gehören. In deinem Beispiel versuchst du in Initialisierungsliste der Klasse
EnemyElemente der KlasseCharakterzu initialisieren, was nicht erlaubt ist.Echt? Dann habe ich mich vom Compiler reinlegen lassen.
http://ideone.com/t1AHr
-
Mein Posting bezieht sich auf das Problem von theuring, Zeile 36. Da versucht er Elemente der Basisklasse per Initialisierungsliste zu intialisieren.
@cooky451
Du rufst denCharakter Konstruktorauf, was völlig ok ist.
-
Ach so und ich dachte schon ich müsste gefühlte 50k Zeilen Code umschreiben.

-
Warum muss das alles nur so kompliziert sein?

Ich komme mit eurem Code einfach nicht klar, werde wohl meinen inneren Schweinehund überwinden müssen und nochmal Basics lernen, damit ich endlich ein einfaches, kurzes, aber "ordentlich" geschriebenes, textbasiertes RPG hinbekomme...

@cooky451: In deinem Link geht es um das "Dungeons & Dragons v3.5" Regelwerk - ich möchte aber ein schönes, "altes" "Advanced Dungeons & Dragons" (v2) Regelwerk verwenden. Dieser ganze Kram ab v3 ist nämlich total Einsteigerfreundlich (auch eher für jüngere Spieler, finde ich) - und das will ich ja nicht

Ganz zu schweigen davon, das D&D3.5 vielleicht wenige hundert Seiten Regelwerk hat - AD&D(2.0) hat mehrere Tausend
Auch vielend Dank für eure vielen Hinweise zur Initiative - aber das Regelwerk sieht "Gleichzeitigkeit" vor und so wird es auch von mir umgesetzt werden
Ich werde versuchen, mir jetzt nochmals Pointer und Konstruktoren reinzuziehen, bis ich es wirklich zu 100% verstehe. Wenn ich zu blöd dafür sein sollte... dann weiß ich auch noch nicht. Jedenfalls muss ein Text-RPG mit rudimentärem AD&D 2nd Edition Kampfsystem her...

-
Hallo C++ler,
ich habe mich nun nochmal mit der Materie und auch dem von cooky451 gegebenen Beispiel (http://ideone.com/t1AHr) beschäftigt. Wenn ich das Beispiel so (oder leicht abgeändert) um setze, kann ich aber immer noch keinen Zugriff auf die Membervariablen durchführen. Ein
Enemy Gegner("test", 10, 10); cout << Gegner.current_life << endl;bringt mir den Fehler
1>c:\users\tobias\documents\visual studio 2010\projects\rpg01\rpg01\main.cpp(33): error C2039: 'current_life': Ist kein Element von 'Enemy'
1> c:\users\tobias\documents\visual studio 2010\projects\rpg01\rpg01\main.cpp(21): Siehe Deklaration von 'Enemy'Wie bekomme ich also diese Sache endgültig in den Griff?
Wäre nett, wenn da jemand kurz & knapp helfen kann
P.S.: Hier nochmal mein kompletter Code: http://ideone.com/q3DxX
Die ganzen Includes sind nur drin, weil später noch Code kommt, der diese ganzen Libs braucht.
-
Die Variable heißt:
current_life_
und nicht:
current_life
-
oha - wie peinlich. Naja, dann weiß ich jetzt ja Bescheid. Oder auch nicht. Jedenfalls funktioniert es nun so, wie es soll - Danke!
Ich schaue mir die Sache nochmal genau an, um selber zu verstehen.
Dann wird Morgen das größte RPG aller Zeiten mit 5D-Grafik und 22.2 Sound sowie 50.000h Mindestspielstunden fertig sein...
P.S.: ich habe nun verstanden, war ja auch gar nicht schwer. Ich dachte halt bisher, dass in der Enemy-Klasse die Variablen-Namen "überschrieben" werden.
-
Hallo,
kann ich einen Vector an eine Funktion übergeben, damit ich in dieser Funktion mit dem Vector arbeiten kann? Ganz speziell: Ich möchte dem Vector (der eine Klasse als Inhalt hat) Objekte hinzufügen (push_back).
-
Mit einer Referenz:
void my_super_function(std::vector<int>& mynumbers) { mynumbers.push_back(42); }
-
Vector: Objekte löschen / Speicher freigeben / Speicherloch schließen
volkard schrieb:
vector<cCharakter*> dieGegner;//Sorry, Speicherloch vorprogrammiert. Läßt sich aber besser suchen. //Speicherloch dann wegmachen mit ptr-Container. Erstmal hinkriegen, schlage ich vor.Ja, genau da bin ich nun. "Der Ork wurde vom Spieler in zwei Hälften gespalten. Tote Orks haben aber nichts mehr im Gegner-Vector zu suchen..."
Also habe ich schon mit .erase was versucht, dass gibt natürlich einen netten Laufzeitfehler (mein erster
). Das Problem ist wohl auch, das gerade im Moment des Löschens ein Iterator durchläuft.
Mein Wunschtraumcode://Alle Gegner auf Tod prüfen for (i=GegnerV.begin();i<GegnerV.end();i++) { if ( i->current_hp <= 0) { cout << i->name << " ist tot."; GegnerV.erase(i); cout << " Und wird rausgeschmissen." << endl; } }Wenn ich nach "ptr_vector" google, lande ich bei Boost. Ist das ein muss? Ich meine, früher oder später brauche ich wohl sowieso eine ordentliche Lib für Zufallszahlen, aber vielleicht sollte ich erstmal weiter die STL kennenlernen...
-
thuering schrieb:
Wenn ich nach "ptr_vector" google, lande ich bei Boost. Ist das ein muss?
Eher eine gute Empfehlung. Dynamische Speicherverwaltung in Containern wird schnell mühsam und fehleranfällig, der benötigte Code für eine korrekte Behandlung tendiert zur Unübersichtlichkeit. Wenn es sich vermeiden lässt, speichere keine Zeiger mit Besitz in Containern.
Aber wenn du dich mit der STL noch nicht auskennst, bringen dir die Boost-Container auch nicht allzu viel. Lies dich am besten mit einem guten C++-Buch ein, hier auf der Artikelseite gibts auch Einführungen in die STL.
Generell kann auch
std::remove_iferwähnt werden, das in Kombination miterase()alle Elemente rauswirft, die eine gewisse Bedingung erfüllen (z.B.current_hp <= 0). Ist beistd::vectorauch viel effizienter als mehrereerase()-Aufrufe, weil nachfolgende Elemente nicht jedes Mal umkopiert werden müssen, wenn eine Lücke entsteht.