Cplusplus ConsolenSpiel Programmiert. Könnt ihr da durchbklicken?



  • Hallo,

    ich habe gerade ein kleines Spiel in Cplusplus programmiert, welches ich GoblinGame 2 genannt habe :). Den ersten Teil war eher so ein grober Aufbau des Spiels und es ist auch jetzt nicht wirklich fertig, ist aber eh nur zu Lernzwecken.

    Ich habe es wirklich nur simpel gehalten:
    Der Spieler hat am Anfang die 4 Optionen das gesichtete Monster anzugreifen, weiter Ausschau zu halten, sich auszuruhen auf kosten der gesammelten XP oder aufzugeben.

    Wenn er angreift hat er wieder 3 Optionen, Angreifen, Blocken oder Fliehen.
    Blocken gibt einen Verteidigungsbonus von 50% und noch ein wenig zufälligen Wert.

    Meine Frage an euch ist, was ihr von dem Aufbau des Codes haltet, wie sauber ihr ihn einschätzt (bzw. auch nicht), was ihr vom Aufbau her ändern würdet und so weiter.

    Es gibt immer ein paar Fehler im Spiel selbst, wie das unausgeglichene LVL-System und so und man bekommt immer weniger Verteidigung mit jeden LVL, sowie viel zu viel Angriff, aber darauf kommt es erstmal nicht an.

    Es würde mich auch noch freuen wenn ihr mir erklären könntet, bzw Vorschläge liefern könntet, wie ich dort am besten noch ein gewicht beim auswählen des Monsters bekommen könnte, also das z.B. Die Schlange und der Goblin öfter ausgewählt werden als der Rest der Monster.

    MfG BotMaster3000

    #include "stdafx.h"
    #include "iostream"
    #include "string"
    #include "random"
    #include "ctime"
    
    enum Monster
    {
    	MONSTER_GOBLIN,
    	MONSTER_SNAKE,
    	MONSTER_ORC,
    	MONSTER_GIANT,
    	MONSTER_TROLL,
    	MONSTER_DRAGON,
    	MAX_MONSTER
    };
    
    std::string chooseMonster(Monster &monsterType)
    {
    	switch (monsterType)
    	{
    	case MONSTER_GOBLIN:	return "Goblin"; break;
    	case MONSTER_SNAKE:		return "Schlange"; break;
    	case MONSTER_ORC:		return "Orc"; break;
    	case MONSTER_GIANT:		return "Riese"; break;
    	case MONSTER_TROLL:		return "Troll"; break;
    	case MONSTER_DRAGON:	return "Drache"; break;
    
    	default: return "ERROR"; break;
    	}
    }
    
    struct Stats
    {
    	int health;
    	int maxHealth;
    	int dmg;
    	int def;
    	int xp;
    	int lvl;
    	std::string artikel;
    	std::string unbesArtikel;
    };
    
    int main();
    void battleInitiator(Stats &player);
    
    Stats initPlayerStats()
    {
    	Stats player;
    	player.health = 100;
    	player.maxHealth = 100;
    	player.dmg = 5;
    	player.def = 1;
    	player.xp = 0;
    	player.lvl = 1;
    	return player;
    }
    
    Stats initMonsterStats(Monster &monsterType)
    {
    	Stats monster;
    	switch (monsterType)
    	{
    	case MONSTER_GOBLIN:
    		monster.health = 10;
    		monster.maxHealth = 10;
    		monster.dmg = 5;
    		monster.def = 3;
    		monster.xp = 10;
    		monster.artikel = "der";
    		monster.unbesArtikel = "einen";
    		break;
    	case MONSTER_SNAKE:
    		monster.health = 1;
    		monster.maxHealth = 1;
    		monster.dmg = 1;
    		monster.def = 1;
    		monster.xp = 1;
    		monster.artikel = "die";
    		monster.unbesArtikel = "eine";
    		break;
    	case MONSTER_ORC:
    		monster.health = 30;
    		monster.maxHealth = 30;
    		monster.dmg = 15;
    		monster.def = 8;
    		monster.xp = 30;
    		monster.artikel = "der";
    		monster.unbesArtikel = "ein";
    		break;
    	case MONSTER_GIANT:
    		monster.health = 200;
    		monster.maxHealth = 200;
    		monster.dmg = 30;
    		monster.def = 50;
    		monster.xp = 50;
    		monster.artikel = "der";
    		monster.unbesArtikel = "ein";
    		break;
    	case MONSTER_TROLL:
    		monster.health = 45;
    		monster.maxHealth = 45;
    		monster.dmg = 12;
    		monster.def = 9;
    		monster.xp = 35;
    		monster.artikel = "der";
    		monster.unbesArtikel = "ein";
    		break;
    	case MONSTER_DRAGON:
    		monster.health = 500;
    		monster.maxHealth = 500;
    		monster.dmg = 300;
    		monster.def = 75;
    		monster.xp = 1000;
    		monster.artikel = "der";
    		monster.unbesArtikel = "ein";
    		break;
    	}
    
    	return monster;
    }
    
    void fight(Stats &player, Stats &monster, std::string &monsterName, int &playerOption, int &monsterOption)
    {
    	std::cout << "Du ";
    	switch (playerOption)
    	{
    	case 1: std::cout << " greifst an.\n"; break;
    	case 2: std::cout << " blockst den angriff.\n"; break;
    	case 3: std::cout << " fliehst.\n"; battleInitiator(player); break;
    	}
    
    	std::cout <<"Dein Feind, "<< monster.artikel << " " << monsterName << " ";
    	switch (monsterOption)
    	{
    	case 0: std::cout << "greift an.\n"; break;
    	case 1: std::cout << "greift an.\n"; break;
    	case 2: std::cout << "blockt.\n"; break;
    	}
    
    	int damagePlayer;
    	switch (playerOption)
    	{
    	case 1:
    		damagePlayer= player.dmg + (rand() % player.dmg) - monster.def - (rand() % monster.def);
    		if (damagePlayer <= 0) damagePlayer = 0;
    		std::cout << "\nDu greifst den Feind an und machst insgesamt " << damagePlayer << " Schaden.\n";
    		monster.health -= damagePlayer;
    		break;
    	default:
    		break;
    	}
    
    	int damageMonster;
    	switch (monsterOption)
    	{
    	case 0 :
    		damageMonster = (monster.dmg + (rand() % monster.dmg)) - (player.def - (rand() % player.def));
    		if (damageMonster <= 0) damageMonster = 0;
    		std::cout << "\n" << monster.artikel << " " << monsterName << " greift an und macht insgesamt einen Schaden von " << damageMonster << "\n";
    		player.health -= damageMonster;
    		break;
    	case 1 :
    		damageMonster = (monster.dmg + (rand() % monster.dmg)) - (player.def - (rand() % player.def));
    		if (damageMonster <= 0) damageMonster = 0;
    		std::cout << "\n" << monster.artikel << " " << monsterName << " greift an und macht insgesamt einen Schaden von " << damageMonster << "\n";
    		player.health -= damageMonster;
    	default:
    		break;
    	}
    }
    
    void initCombat(Stats &player, Stats &monster, std::string &monsterName)
    {
    	while (player.health > 0 && monster.health > 0)
    	{
    		std::cout << "\n\nDu hast folgende Werte:\n";
    		std::cout << "HP: " << player.health << "\n";
    		std::cout << "DMG: " << player.dmg << "\n";
    		std::cout << "DEF: " << player.def << "\n";
    		std::cout << "XP: " << player.xp << "\n";
    		std::cout << "LVL: " << player.lvl << "\n";
    
    		std::cout << "\n" << monster.artikel << " " << monsterName << " hat folgende Werte:\n";
    		std::cout << "HP: " << monster.health << "\n";
    		std::cout << "DMG: " << monster.dmg << "\n";
    		std::cout << "DEF: " << monster.def << "\n";
    
    		std::cout << "\nWas willst du machen?\n";
    		std::cout << "1: Angreifen\n";
    		std::cout << "2: Blocken (DEF+50%)\n";
    		std::cout << "3: Fliehen\n";
    
    		int playerOption;
    		std::cin >> playerOption;
    		int playerDefIncrease = player.def * 0.5;
    		if (playerOption == 2) player.def += playerDefIncrease;
    
    		int monsterDefIncrease = monster.def * 0.5;
    		int monsterOption = rand() % 3;
    		if (monsterOption == 2) monster.def += monsterDefIncrease;
    
    		fight(player, monster, monsterName, playerOption, monsterOption);
    
    		if (playerOption == 2) player.def -= playerDefIncrease;
    		if (monsterOption == 2) monster.def -= monsterDefIncrease;
    	}
    
    	if (monster.health <= 0)
    	{
    		std::cout << "Gratulation, du hast das Monster besiegt.\n";
    		std::cout << "Du hast dir " << monster.xp << "XP verdient.\n";
    		player.xp += monster.xp;
    
    		if (player.xp - 100 >= 0)
    		{
    			std::cout << "Gratulation, du bist ein Level aufgestiegen!\n" << std::endl;
    			player.lvl += 1;
    			player.xp -= 100;
    			std::cout << "Du bist jetzt Level " << player.lvl << "!\n";
    			player.maxHealth += player.maxHealth / 10;
    			player.health = player.maxHealth;
    			player.dmg *= + (player.dmg / 4 + 1);
    			player.def *= + (player.def / 4 + 1);
    		}
    	}
    
    	if (player.health <= 0)
    	{
    		std::cout << "GAME OVER\nGAME OVER\nGAME OVER\nGAME OVER\nGAME OVER\nGAME OVER\n";
    		std::cout << "Nochmal? (y/n)";
    		char option;
    		std::cin >> option;
    		if (option == 'y')
    			main();
    		else;
    	}
    
    	std::cout << "\n\n";
    }
    
    void battleInitiator(Stats &player)
    {
    	while (player.health > 0)
    	{
    		srand(static_cast<unsigned int>(time(0)));
    		Monster monsterType = static_cast<Monster>(rand() % MAX_MONSTER);
    		std::string monsterName = chooseMonster(monsterType);
    		Stats monster = initMonsterStats(monsterType);
    
    		std::cout << "Du siehst " << monster.unbesArtikel << " " << monsterName << ".\nWie gehst du vor?\n";
    		std::cout << "1. In den Kampf ziehen.\n";
    		std::cout << "2. Ausweichen und weiter Ausschau halten.\n";
    		std::cout << "3. Ausruhen und HP auffuellen. Kostet die Haelfte der XP.\n";
    		std::cout << "4. Aufgeben (GAME OVER)\n";
    		int decision;
    		std::cin >> decision;
    		switch (decision)
    		{
    		case 1: initCombat(player, monster, monsterName); break;
    		case 2: battleInitiator(player); break;
    		case 3: player.health = player.maxHealth; player.xp /= 2; break;
    		case 4: 
    			std::cout << "Willst du das wirklich?(y/n)\n";
    			char x;
    			std::cin >> x;
    			switch (x)
    			{
    			case 'y': exit(0); break;
    			case 'n': battleInitiator(player); break;
    			default: std::cout << "ERROR\n"; battleInitiator(player);
    			}
    		}
    	}
    }
    
    int main()
    {
    	Stats player = initPlayerStats();
    	battleInitiator(player);
    
        return 0;
    }
    


  • Nur eine kurze Anmerkung: dein battleInitiator ruft sich rekursiv auf,
    was nach sehr langem Spielen unweigerlich zum Absturz führt.
    Also nur, falls ein Spieler genug Motivation aufbringt,
    ewig gegen Monster zu kämpfen 😉

    EDIT: Als Lösung des Problems könnte deine Funktion z.B. einen bool zurückgeben.
    In deiner main-Funktion hast du dann eine Schleife, die so lange battleInitiator wieder aufruft, bis mal false kommt.
    Beispiel:

    int main()
    {
        Stats player = initPlayerStats(); 
        while (battleInitiator(player));
        return 0;
    }
    

    EDIT2: Bei genauerer Betrachtung fällt dann auf, dass deine Funktionen sich generell gegenseitig aufrufen, aber eigentlich nie zurückkehren.
    Genauso ist meine vorgestellte Schleife unnötig, da du ja in battleInitiator bereits eine Schleife hast, die tut, was du willst.
    Du benutzt diese Schleife nur nicht wirklich, weil du anstatt in den nächsten Schleifendurchlauf zu gehen die Funktion einfach nochmal aufrufst...

    possible stack overflows everywhere!



  • Ein erster Blick:

    - mehrere switch-Statements auf dieselbe Sache lassen darauf schließen, dass es anders besser ginge. Dieses Pattern kommt bei dir mehrfach vor. Unter anderem gefällt mit der mehrmalige switch auf MONSTER_ENUM nicht. An genau einer Stelle solltest du die Fallunterscheidung machen und dann z.B. eine Monsterfabrik oder ähnliches haben.

    - Überhaupt sind all deine Objekte reine structs ohne Funktionen.

    - Überprüfe mal die Signaturen deiner Funktionen: ein enum wird als Referenz übergeben?! Macht man nur dann, wenn man es wirklich ändern will - das kannst du einfach kopieren. Ansonsten bitte mal auf const-correctness achten.

    - Die Standard-Header includet man nicht mit "" (sucht auch im lokalen Pfad), sondern mit spitzen Klammern <>.

    - du machst srand innerhalb einer Schleife. Nicht gut. Das Thema hatten wir gerade und wie man es in C++ macht, steht u.a. hier: https://www.c-plusplus.net/forum/340064#2511876

    Da ist bestimmt noch mehr, ich habe nur keine Zeit mir das genauer anzusehen 🙂



  • Das Programm ist einfach nur C mit cout/cin, anstatt modernes C++, aber ich weiß, daß es für einen Anfänger schwierig ist, das alles gleich umzusetzen.

    Auffällig finde ich aber immer wieder, daß dann immer gleich Copy&Paste-Code dabei entsteht (anstatt zu versuchen, den Code wieder zu refaktorieren, um kürzeren und wiederverwendbareren Code zu erhalten).

    Als konstruktiven Tipp noch:
    Gerade bei Strukturen lohnt sich, die vordefinierten Werte als Konstanten vorzuhalten, anstatt dafür Programmcode zur Initialisierung zu schreiben:

    const Stats monster_stats[MAX_MONSTER]
    {
      // Name auch zur Struktur hinzugefügt - erspart extra Funktion 'chooseMonster' (sowieso unpassender Name)
      { "Goblin", 10, 10, 5, 3, 10, "der", "einen" },
      { ... },
    };
    
    const Stats& getMonsterStats(Monster monsterType)
    {
        const Stats& monster = monster_stats[monsterType];
    
        return monster;
    }
    


  • @Drako: Oh, ich wusste vorher garnicht was für Auswirkungen das hat, habs mir auch nochmal genauer durchgelesen und korrigiert, danke 🙂

    @Wob: Ich hatte bisher noch kein Objekt Orientiertes Programmieren 🙂
    Ich gehe hauptsächlich nach diesem Tutorial vor:
    http://www.learncpp.com/

    Ich fange heute erst mit dem Objekt Orientierten Programmieren an damit an.

    Danke auch für den Tipp bezüglich des #include und bezüglich srand();
    Mir ist auch schon aufgefallen, das wenn man schnell genug angreift, oder man nach anderen monstern Ausschau hält, man öfter den selben Gegner hintereinander bekommt, was wohl daran liegt, das der Wert nur einmal die Sekunde erneuert wird.. .
    Ist korrigiert 🙂

    @Th69: Yeah meine Schreibweise ist nicht unbedingt die beste 🙂
    Bin eben noch relativ neu.

    Ich werde mir das nochmal angucken und dementsprechend umändern, danke 🙂

    Danke für die Antworten, ich werds mir zu Herzen nehmen und den Code nochmal neu schreiben, hoffentlich in besserer Weise als vorher 🙂
    MfG BotMaster3000


Anmelden zum Antworten