C++ lernen, Beispiel: RPG (Console)
-
Nexus schrieb:
Nathan schrieb:
Braucht man das denn? IdR nicht.
Doch, wenn man sicherstellen will, dass der Benutzer das zuletzt Geschriebene auf der Konsole sieht.
Wann will man denn, dass der User etwas sowieso sieht?
-Vor Eingaben -> passiert sowieso
-Vor Programmende -> passiert sowiesoIch benutze std::cout lediglich als Gegenstück für std::cin.
Sprich, mit std::cout werden z.B. die Beschreibung für die nächste Eingabe o.ä. ausgeben oder Ergebnisse eines Programmes. Und in beiden Situationen wird kurz danach geflusht.
Für Log/Fehlerausgaben nutz ich std::cerr/std::clog. Und die hab ich nicht gebuffert.
std::endl brauch ich persönlich nicht.Du solltest nicht undifferenziert das Dogma von den anderen nachplappern,
'\n'
sei immer besser alsstd::endl
.Das hab ich nie behauptet.
-
Nexus schrieb:
Nathan schrieb:
Braucht man das denn? IdR nicht.
Doch, wenn man sicherstellen will, dass der Benutzer das zuletzt Geschriebene auf der Konsole sieht.
Du solltest nicht undifferenziert das Dogma von den anderen nachplappern,
'\n'
sei immer besser alsstd::endl
. Ursprünglich war die Situation nämlich, dass Leute nurstd::endl
beigebracht bekamen und dann das auch in Datei-Streams, Logs und überall brauchten, wo es tatsächlich einen negativen Einfluss auf die Performance hat. Das ist das Problem, nicht einendl
nach einem Block von Konsolenausgaben.Also wo braucht man endl?
Braucht dieses Programm endl?
Was kümmert es mich, daß die Leute damals endl beigebracht bekamen und es noch in allen schlechteren Fachbüchern steht?
Du solltest nicht undifferenziert guten Ratschlägen widersprechen.
-
Also soweit ich das verstanden habe, ist \n einfacher zu benutzen in einem längeren Fließtext ohne Variablen etc., wohingegen endl überall da angewendet wird, wo \n mangels Textausgabe zu umständlich wäre. Kann man das so sagen?
Ich habe jetzt mein Programm unterteilt in mehrere .cpp + .h Files.
Funktioniert soweit.Das Problem mit der OS Erkennung hat sich nur zum Teil gelöst. Windows wird immernoch nicht erkannt, stattdessen kommt eine Fehlermeldung wegen des Linux Befehls.
Deshalb habe ich angefangen, unter dem Menüpunkt Settings die Möglichkeit zur Einstellung des OS zu geben.Ich habe versucht, eine settings.txt Datei zu schreiben/erstellen, die die Zeile
OS=Windows
enthält.
Das klappt auch, allerdings scheitert es noch am Finden von "OS=" und Auslesen des nachfolgenden "Windows", und anschließender Ersetzung durch "Linux".
Alle Tutorials zu dem Thema zeigen entweder nur die "Grundlagen" vom File-handling, oder sind so theoretisch dass ich nicht durchblicke.Kann mir jemand zeigen, wie ich das umsetzen kann?
Mein Test-Code sieht bisher so aus:int main(){ string a; ofstream writefile; writefile.open("test.txt"); writefile << "dies ist ein test\n" "und noch ein test." "os=windows" "test" ""; writefile.close(); ifstream readfile; readfile.open("test.txt"); while(getline(readfile,a)){ size_t pos = str.find("OS="); } readfile.close(); system("pause"); }
Kennt jemand ein Tutorial, das anschaulich zeigt, wie man
- string in einer datei suchen
- string löschen
- string einfügen
kann?
-
Komplett falscher Ansatz.
Du kannst nicht dynamisch das OS wechseln.
Das Windows nicht erkannt wird, liegt wohl daran, dass dein Compiler kein entsprechendes Makro setzt.
Siehe: http://sourceforge.net/p/predef/wiki/OperatingSystems/
Probiers mal mit _WIN32.
-
Nathan schrieb:
Komplett falscher Ansatz.
Du kannst nicht dynamisch das OS wechseln.Inwiefern?
Nathan schrieb:
Probiers mal mit _WIN32.
-
origami schrieb:
Nathan schrieb:
Komplett falscher Ansatz.
Du kannst nicht dynamisch das OS wechseln.Inwiefern?
Du willst ja irgendwo aus einer Datei die Einstellung laden.
Und dein clear-Code sieht dann so aus, richtig?void clear_screen(){ if (os == windows) system("CLS"); else system("clear"); }
In diesem Fall mag das ja noch funktionieren, aber nehmen wir mal an du machst das Löschen "richtig", nämlich über die API des OS:
void clear_screen() { if (os == windows) { // Code grad von StackOverflow kopiert, keine Garantie und so COORD topLeft = { 0, 0 }; HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO screen; DWORD written; GetConsoleScreenBufferInfo(console, &screen); FillConsoleOutputCharacterA( console, ' ', screen.dwSize.X * screen.dwSize.Y, topLeft, &written ); FillConsoleOutputAttribute( console, FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE, screen.dwSize.X * screen.dwSize.Y, topLeft, &written ); SetConsoleCursorPosition(console, topLeft); } else { // Irgendwelche Linux-API Systemaufrufe, die ich nicht kenne } }
Wenn du nun versuchst, dein Programm unter Linux zu kompilieren, wird sich der Compiler beschweren, da die ganzen Funktionen etc. nicht bekannt sind. Und umgekehrt unter Windows. Die OS Entscheidung muss zur Compiletime ablaufen.
-
Nathan schrieb:
Wenn du nun versuchst, dein Programm unter Linux zu kompilieren, wird sich der Compiler beschweren, da die ganzen Funktionen etc. nicht bekannt sind. Und umgekehrt unter Windows. Die OS Entscheidung muss zur Compiletime ablaufen.
Achso, ja ab einem bestimmten Entwicklungsstand geht das nicht mehr, klar. Aber grafisch will ich erstmal garnichts machen, nichtmal WindowsAPI.
Farben wären zwar auch jetzt schon nett, aber farbiger Text bringt mir nichts, solange ich noch kein Spiel habe das Charaktere/Dateien erstellen/laden/verändern kann.Die "Beta" soll erstmal rein Konsolen-basiert sein. Auch die erste Version einer Karte soll rein ASCII-basiert sein, und auch von der bin ich noch weit entfernt.
Ich will erstmal nur die Basics von C++ verstehen. Es steht im Moment weniger ein "echtes Spiel" im Vordergrund, als das C++ lernen.
-
Wenn du erstmal nur C++ lernen möchtest, dann programmiere ruhig erstmal platformabhängig. Mach dir keine Sorgen, ob das Programm auch unter Linux oder auf deinem Toaster läuft, lern erstmal die Sprache vernünftig. Wenn du ein bisschen weiter bist wirst du von alleine sehen, dass alles was du bisher geschrieben hast sich noch viel besser schreiben lässt.
Und es ist eine sehr gute Idee dich erstmal auf die Konsole zu beschränken!
-
Biolunar schrieb:
Wenn du ein bisschen weiter bist wirst du von alleine sehen, dass alles was du bisher geschrieben hast sich noch viel besser schreiben lässt.
Vorschläge willkommen
-
Nun ja, Nathan hat insofern recht als dass es einfach üblich ist das OS zur Compilezeit zu unterscheiden. Was generell auch Sinn macht, weil du kannst eine für Windows kompilierte .exe eh nicht auf Linux ausführen. Was eine "Settings-Datei" angeht, da würde ich gar nicht erst versuchen irgendetwas in der Datei zu suchen, einfach komplett auslesen und am Ende wieder komplett überschreiben. Am besten so in der Art dass du eine Datei im Stil von
setting1=value1
setting2=value2etc. hast, dann liest du diese Key-Wert Paare einfach in eine std::map, und am Ende des Programms überschreibst du die Datei komplett mit neuen Key-Wert Paaren. (Damit alle Änderungen übernommen werden.) Ich gebe dir hier mal eine Klasse die ich irgendwann mal dafür benutzt habe, aber ich bin mir leider nicht sicher wie viel dir das hilft, denn du scheinst von Klassen und Templates noch nicht so viel Ahnung zu haben. Vielleicht lohnt es doch sich die Sprache noch ein bisschen weiter anzugucken.
#ifndef CONFIGURATION_CLASS_HPP #define CONFIGURATION_CLASS_HPP #include <cctype> #include <string> #include <map> #include <fstream> #include <sstream> class configuration { bool use_file_; std::string filename_; std::map<std::string, std::string> config_; public: configuration(std::string filename, bool use_file = true) : use_file_(use_file) , filename_(std::move(filename)) { if (use_file_) { std::ifstream infile(filename_); std::string key, value; while (std::getline(infile, key, '=') && std::getline(infile, value)) set(to_lower(key), to_lower(value)); } } configuration(const configuration&) = delete; configuration(configuration&& other) : use_file_(other.use_file_) , filename_(std::move(other.filename_)) , config_(std::move(config_)) {} ~configuration() { flush_to_file(); } void flush_to_file() const { if (use_file_) { std::ofstream outfile(filename_); for (auto& pr : config_) outfile << pr.first << "=" << pr.second << '\n'; } } bool contains(const std::string& value) const { return config_.find(value) != config_.end(); } bool get_string(std::string& value, const std::string& key) const { auto found = config_.find(to_lower(key)); if (found != config_.end()) { value = found->second; return true; } return false; } void set_string(const std::string& key, const std::string& value) { config_[to_lower(key)] = to_lower(value); } template <typename T> bool get(T& value, const std::string& key) const { std::string str_value; if (get_string(str_value, key)) { std::stringstream ss; ss << str_value; ss >> value; return ss.good(); } return false; } template <typename T> void set(const std::string& key, const T& value) { std::stringstream ss; ss << value; set_string(key, ss.str()); } template <typename T> bool get_or_set(T& value, const std::string& key) { if (!get(value, key)) { std::stringstream ss; ss << value; set(key, ss.str()); return false; } return true; } private: static std::string to_lower(std::string s) { for (auto& c : s) c = static_cast<char>(std::tolower(c)); return s; } }; #endif
#include <iostream> int main() { configuration cfg("config.cfg"); int difficulty = 2; double map_size = 1337.42; std::string player_name = "Bob"; cfg.get_or_set(difficulty, "difficulty"); cfg.get_or_set(map_size, "map_size"); cfg.get_or_set(player_name, "player_name"); std::cout << "difficulty: " << difficulty << '\n'; std::cout << "map_size: " << map_size << '\n'; std::cout << "player_name: " << player_name << '\n'; }
Ist natürlich noch endless erweiterbar mit Kategorien etc., aber wie gesagt ist relativ kurz gehalten. Hab ich damals nur aus der Not heraus gebaut schnell irgendwas zu haben was funktioniert. Hoffe ich habe bei ein paar Änderungen keine Bugs eingebaut.
-
cooky451 schrieb:
Was eine "Settings-Datei" angeht, da würde ich gar nicht erst versuchen irgendetwas in der Datei zu suchen, einfach komplett auslesen und am Ende wieder komplett überschreiben. Am besten so in der Art dass du eine Datei im Stil von
setting1=value1
setting2=value2etc. hast, dann liest du diese Key-Wert Paare einfach in eine std::map, und am Ende des Programms überschreibst du die Datei komplett mit neuen Key-Wert Paaren.
Beim Charakter, ein paar Monstern und NPCs, und den Settings kommt nicht viel zusammen, aber mein vorrangiges Ziel ist ja nicht, möglichst schnell auf schlechte Programmier-Art ein Spiel auf die Beine zu stellen, sondern es gleich von Anfang an richtig zu machen.
Mindestens bei der Map würde ich deshalb nur das Feld (+ vllt ein paar umliegende) in den Speicher laden, und die Tiles nur aufrufen, wenn der Charakter in die Nähe kommt.Das Programm müsste die Daten in einer bestimmten Zeile von/bis zu einem bestimmten Offset lesen.
Ich bin mir sicher, dass so etwas nicht schwer zu programmieren ist, ich kenne nur einfach die Befehle dafür nicht. Man braucht eigentlich nur:
- Datei öffnen (krieg ich hin)
- Zeile lesen (krieg ich auch hin)
- String auf eine bestimmte Zeichenfolge untersuchen (Settings)
- Zeile von/bis zu einem festgelegten Offset einlesen (Map)
- Daten innerhalb eines bestimmten Offset-Bereichs überschreiben
- Daten an einem bestimmten Offset zwischen vorhandene Zeichen einfügen, ohne zu überschreiben
- Datei schließen (krieg ich wieder hin)Bisher weiss ich zwar, wie ich in einer Zeile Zeichen einlesen kann, allerdings immer nur bis zum nächsten Whitespace.
cooky451 schrieb:
Ich gebe dir hier mal eine Klasse die ich irgendwann mal dafür benutzt habe, aber ich bin mir leider nicht sicher wie viel dir das hilft, denn du scheinst von Klassen und Templates noch nicht so viel Ahnung zu haben. Vielleicht lohnt es doch sich die Sprache noch ein bisschen weiter anzugucken.
Danke für den Code, ich werd mal versuchen, da durch zu steigen. Sieht auf den ersten Blick noch recht komplex aus.
-
Ich rede nur von einem config-file. Ein map-file ist etwas komplett anderes.
Da muss man sich dann erst mal überlegen wie man das aufbauen will, welche Daten man woher bekommt, welche Daten man überhaupt braucht etc. da gibt es unendlich viele Möglichkeiten das aufzuziehen. Mit seekg kannst du übrigens bestimmte Teile einer Datei lesen. http://en.cppreference.com/w/cpp/io/basic_ifstream
-
Jau, habs hinbekommen, mit seek/tellg/p, setw(), substr(), find().
Danke nochmal!Im Prinzip ist es also sinnvoll zu wissen, wie lang die Zeilen in einer Datei einmal sein werden, wenn man später an einzelnen Zeilen basteln will.
Mal eine generelle Frage: Gibt es eine Länge, nach der bei C++ automatisch ein '\n' erfolgt, oder könnte ich eine einzige Zeile mit 10.000 Zeichen schreiben?
-
Nein, Zeilenumbrüche sind oft nur eine Konzession an die Lesbarkeit. Du kannst beliebig große Textdateien erzeugen, die nur vom verfügbaren Speicherplatz begrenzt sind.
-
DocShoe schrieb:
_________________
Die fünf häufigsten Anzeichen für Faulheit:
1.Ich würde gerne eine "Sleep" Funktion aufrufen, aber keine der folgenden geht:
Sleep()
usleep()
sleep()
sleep_for()<chrono> <thread> <unisth.h> <ctime> und <windows.h> hab ich drin.
Weiss jemand Rat?
-
-
Biolunar schrieb:
Was heißt „geht nicht“?
Das heisst in diesem Fall die Meldung:
'sleep_for' undeclaredAusserdem bekomme ich:
chrono: No such file or directory
thread: No such file or directory
(obwohl ich beide #included habe)Versuche es gerade mit Dev-C++ 4.9.9.2 unter Windows.
-
origami schrieb:
Versuche es gerade mit Dev-C++ 4.9.9.2 unter Windows.
Diese IDE ist dermassen antik, da kannst du C++11 gleich vergessen.
Nimm Visual Studio 2013 Express oder eine neue Code::Blocks-Version.
-
Code::Blocks habe ich auch, allerdings bekomme ich da auch den Fehler:
'sleep_for' was not declared in this scope
-
Compilerflag
-std=c++11