Win32-Console 2D MAP-Game



  • Hey Leute,
    ich habe nach langer Zeit wieder angefangen C++ zu programmieren (ich hatte viel mit der Schule zu tun) und habe ein neues Spiel geschrieben in der eine einfache Map gezeichnet wird, in der ein paar Objekte vorhanden sind und man sich als Spieler frei mit "W A S D" bewegen kann.

    Bis jetzt klappt alles super und ich bin noch in der Entwicklung des Spiels, doch bevor ich jetzt anfange dieses Spiel etwas größer und umfangreicher zu gestalten, möchte ich gerne von euch noch wissen wie sauber mein Quelltext ist, da ich hier bisher bekannt bin als der Typ mit einer miserablen Schreibart.

    Daher bitte ich euch kurz durch den Quelltext zu schauen, vielleicht das Spiel auszuprobieren (es ist noch in Entwicklung und man kann es noch nicht als ganzes Spiel betrachten) und mir vielleicht Verbesserungsvorschläge als Rückmeldung zu geben. 🙂

    #include <iostream>
    #include <Windows.h>
    #include <conio.h>
    
    using namespace std;
    
    int main ()
    {
    	// Variables initialization
    	//
        int Points = 0;
        int Key = 0;
        int x_Key = 0;
        int y_Key = 0;
        int x_Map = 40;
        int y_Map = 20;
        int Map[x_Map][y_Map];
        //
        ////
    
        // Reset MAP. MAP = 0 (Nothing " ")
        for (int y = 0; y < y_Map; y++)
        {
            for (int x = 0; x < x_Map; x++)
            {
                Map[x][y] = 0; 
            }   
        }
    
        // Initialization of all objects
        //
        // MAP: Player
        Map[0][0] = 1;
    
        // Map: Wall
        Map[1][0] = 2; Map[1][1] = 2; Map[0][3] = 2; Map[1][3] = 2; Map[2][3] = 2; Map[3][3] = 2;
        Map[3][2] = 2; Map[3][1] = 2; Map[5][0] = 2; Map[5][1] = 2; Map[5][2] = 2; Map[5][3] = 2;
    
        // MAP: Points
        Map[10][10] = 3;
        //
        ////
    
        // Game-Loop (MAP-render of all Map-parts & Control)
        while(true)
        {
        	// Reset all Player-Coordinates on Map
        	for (int y = 0; y < y_Map; y++)
            {
                for (int x = 0; x < x_Map; x++)
                {
                    if (Map[x][y] == 1)
                        Map[x][y] = 0;
                } 
            }
    
        	// Initialize new Player-Coordinates on Map
            Map[x_Key][y_Key] = 1;
    
            // Reset Key-Input
            Key = 0;
    
            // Initialize new Key-Input if kbhit()
            if (kbhit())
                Key = getch();
    
    		// Check, Calculate & Set - The Key-Input
    		//  
            if (Key == 119 && Map[x_Key][y_Key-1] != 2) // Key-Input: w
            {
                if (Map[x_Key][y_Key-1] == 3)
                    Points += 1;
                Map[x_Key][y_Key--] = 1;
            }
            else if (Key == 97 && Map[x_Key-1][y_Key] != 2) // Key-Input: a
            {
                if (Map[x_Key-1][y_Key] == 3)
                    Points += 1;
                Map[x_Key--][y_Key] = 1;
            }
            else if (Key == 115 && Map[x_Key][y_Key+1] != 2) // Key-Input: s
            {
                if (Map[x_Key][y_Key+1] == 3)
                    Points += 1;
                Map[x_Key][y_Key++] = 1;
            }
            else if (Key == 100 && Map[x_Key+1][y_Key] != 2) // Key-Input: d
            {
                if (Map[x_Key+1][y_Key] == 3)
                    Points += 1;
                Map[x_Key++][y_Key] = 1;
            }
            //
            ////
    
    		// Map-Render (includes all objects)  
    		//    
            cout << "==========================================\n";
    
            for (int y = 0; y < y_Map; y++)
            {
                cout << "|";
                for (int x = 0; x < x_Map; x++)
                {
                    if (Map[x][y] == 1)
                        cout << "o";
                    else if (Map[x][y] == 2)
                        cout << "X";
                    else if (Map[x][y] == 3)
                        cout << "P";
                    else
                        cout << " ";
                }
                cout << "|";
                cout << endl;  
            }
    
            cout << "==========================================\n";
            //
            ////
    
    		// Game-Stats   
            cout << "Key ID: " << Key;
            cout << "     Points: " << Points;
    
            // Reset Game-Event
            Sleep(50);
            system("CLS");
        } 
    }
    

    Ich denke schon allein wegen der Spielart, wäre es das beste für jedes Objekt eine Klasse anzufertigen und eine bessere Lösung für die Initialisierung für die Objekte zu finden. Jedoch weiß ich nicht wie ich das in die Tat umsetzen soll und hoffe hier auf hilfe! 🙂

    MfG, Marvin.


  • Mod

    {
        // Variables initialization
        //
        int Points = 0;
        int Key = 0;
        int x_Key = 0;
        int y_Key = 0;
        int x_Map = 40;
        int y_Map = 20;
    

    Alle Variablen sollten so lokal wie möglich deklariert werden. Solche gleich am Anfang der main -Funktion zu deklarieren ist daher extrem schlechter Programmierstil.
    Wegen der Zuweisung in Zeile 61 ist die Initialisierung von Key theoretisch sowieso überflüssig - pack' aber gleich die komplette Deklaration von Key nach Zeile 61!
    (Die Initialisierung ist hier aber nicht per se falsch, sondern richtig, und sollte dir prinzipiell als Angewohnheit bleiben)

    if (Map[x][y] == 1)
                        cout << "o";
                    else if (Map[x][y] == 2)
                        cout << "X";
                    else if (Map[x][y] == 3)
                        cout << "P";
                    else
                        cout << " ";
    

    ➡

    static char const output[] = " oXP";
    cout << output[Map[x][y]];
    

    ➡

    static char const output[] = " oXP";
            for (int y = 0; y < y_Map; y++)
                    cout << '|' << output[Map[x][y]] << "|\n";
    

    IIRC ist die Ausgabe auf Windows Zeilengepuffert.
    Und einzelne Zeichen wo möglich nicht in String- sondern Zeichen-Literale packen. Also "|" ➡ '|' (ist im Code ja demonstriert).

    Map[x_Key][y_Key--] = 1;
    

    Volkard würde dir eins mit dem Braten überziehen! Aber so richtig.

    Die Zeilen 69 bis 92 sind auch alle recht ähnlich, findest du nicht? Verletzung von DRY.

    auto check_key_with_indices = [&]( char ch, std::size_t x_add, std::size_t y_add )
    {
            if (Key == ch && Map[x_Key + x_add][y_Key + y_add] != 2)
            {
                Map[x_Key][y_Key] = 1;
                // Reihenfolge verändert - ist die Äquivalenz vorhanden?
                y_Key += y_add;
                x_Key += x_add;
                if (Map[x_Key][y_Key] == 3)
                    ++Points;
                return true;
            }
            return false;
    }
    
         if( check_key_with_indices('w',  0, -1) )   ;
    else if( check_key_with_indices('a', -1,  0) )   ;
    else if( check_key_with_indices('s',  0,  1) )   ;
    else if( check_key_with_indices('d',  1,  0) )   ;
    

    (Alles ungetestet, keine Garantie)
    Mit einem Makro ginge es potenziell schöner, ist aber nicht gewünscht.

    Und was auch direkt auffällt ist die Tatsache dass du immer neu renderst, obwohl dein Spiel darauf ausgerichtet ist dass nichts passiert solange keine Taste gedrückt wurde (oder?).
    Daher sollte das if (kbhit()) in Zeile 64 wohl eher verschwinden - reicht AFAICS.

    Ein break oder return sehe ich in der Schleife auch nicht. Sollte das Spiel nicht, sobald Q o.ä. gedrückt wurde, abgebrochen werden?

    Und warum Key == 115 ? Warum nicht mit einem Zeichenliteral vergleichen, also in diesem Fall 's' ? Und warum kein std::tolower (bspw. um Caps-Lock zu ignorieren)?
    (Eigentlich könnte man andersherum std::toupper verwenden und mit 'S' vergleichen)

    Warum keine Enumeration als Elementtyp der Map verwenden?

    Kann momentan übrigens nichts testen, da ich unter Linux bin (Portabilität wäre ab einem gewissen Punkt durch platformunabhängige Libs auch nicht schlecht! 🙂 ), verzeih' mir also jedwegige Patzer.
    ~
    Edit³: Bugs im Lambda korrigiert.~



  • Warum wird immer die ganze Konsole gelöscht und alles neu gezeichnet? Das dürfte zu mehr oder weniger heftigem Flackern führen.
    Entweder einfach ohne zu löschen alles übermalen - dann sollte es schon mal nicht flackern, oder, noch viel besser, nur die Positionen übermalen, die sich ändern.
    Wenn eine Spielfigur von 10,11 nach 10,12 läuft, sind im Normalfall nur diese beiden Positionen auf den neuen Stand zu bringen.



  • Danke für die schnellen Antworten.

    Warum wird immer die ganze Konsole gelöscht und alles neu gezeichnet? Das dürfte zu mehr oder weniger heftigem Flackern führen.
    Entweder einfach ohne zu löschen alles übermalen - dann sollte es schon mal nicht flackern, oder, noch viel besser, nur die Positionen übermalen, die sich ändern.
    Wenn eine Spielfigur von 10,11 nach 10,12 läuft, sind im Normalfall nur diese beiden Positionen auf den neuen Stand zu bringen.

    @Belli
    Leider weiß ich nicht wie man nur ganz bestimmte Positionen löschen kann...
    + @Arcoth
    Ich hätte zwar auch getch() nehmen können und nicht kbhit(), dann hätte sich die Konsole nur bei Tastendruck neu gezeichnet, ich möchte aber noch frei bewegende NPC's einfügen.
    Da mein Wissen in C++ stark begrenzt ist, kenne ich nicht die schönere Lösung.
    Es wäre nett wenn du sie mir zeigen könntest. 🙂

    Ein break oder return sehe ich in der Schleife auch nicht. Sollte das Spiel nicht, sobald Q o.ä. gedrückt wurde, abgebrochen werden?

    Und warum Key == 115? Warum nicht mit einem Zeichenliteral vergleichen, also in diesem Fall 's'? Und warum kein std::tolower (bspw. um Caps-Lock zu ignorieren)?
    (Eigentlich könnte man andersherum std::toupper verwenden und mit 'S' vergleichen)

    Warum keine Enumeration als Elementtyp der Map verwenden?

    @Arcoth
    Ich wollte im Nachhinein (da das Spiel noch in Entwicklung ist) ein Exit mit der ESC-Taste einfügen. (Also while(Key != ESC){...} return 0;)
    Zu dem Thema warum ich für die Keys Zahlen verwende und nicht das Zeichen ansich: Was mache ich wenn ich z.B. eine Pfeiltaste oder die ESC-Taste inkludieren möchte? Dann kommen da immer komische Zeichen wenn ich es dann mit cout << Key; teste. Daher fand ich auf Key eine int statt einem char zu setzen für den Anfang einfacher. (Vielleicht kannst du mir da ja ein Lösungsvorschlag geben)
    Eine Enumeration einzusetzen habe ich mir auch überlegt, nur weiß ich nicht wie ich das in einem 2D-Array in die Tat umsetzen kann...

    MfG, Marvin.



  • Moorky Dragonstrike schrieb:

    @Belli
    Leider weiß ich nicht wie man nur ganz bestimmte Positionen löschen kann...

    Dafür solltest Du hier jede Menge Beispiele finden. Suche im Konsolenforum, das ist zwar für neue Beiträge geschlossen, aber da ist reichlich alter, guter Code für die Windows-Konsole zu finden.


  • Mod

    Eine Enumeration einzusetzen habe ich mir auch überlegt, nur weiß ich nicht wie ich das in einem 2D-Array in die Tat umsetzen kann...

    😕
    Die Frage verstehe ich nicht ganz. Du verwendest als Elementtyp des multidimensionalen Arrays die Enumeration.

    // Ich kenne deine Objekte nicht, daher habe ich mal geraten:
    enum BlockType
    {
        nothing,
        human,
        rock,
        player
    };
    
    BlockType Map[x_Map][y_Map];
    

    Statt dann ein Element mit einer Zahl zu vergleichen vergleichst du das Element mit dem entsprechenden Enumerator.

    if( /* [...] */ Map[x_Key+1][y_Key] != rock )
    

    Du könntest dann in die OOP einsteigen indem du für die verschiedenen Bestandteile des Spiels Klassen definierst, darunter natürlich die Umgebung (Map), die darin enthaltenen Objekte, und so weiter.

    Dann kommen da immer komische Zeichen wenn ich es dann mit cout << Key; teste. Daher fand ich auf Key eine int statt einem char zu setzen für den Anfang einfacher. (Vielleicht kannst du mir da ja ein Lösungsvorschlag geben)

    Nun, du vergleichst dann einfach für die Buchstaben mit den Zeichenliteralen 'a' , 's' , usw. und für die Pfeil- und Escape-Taste(n) mit dem entsprechenden ASCII-Code. Keiner hindert dich daran diese zu vermischen.

    Es wäre aber deutlich sinnvoller diese folgendermaßen festzuhalten:

    int const left_key = 'a';
    int const right_key = 'd';
    int const down_key = 's';
    int const up_key = 'w';
    int const escape_key = ...; // bzw. gleich den Namen geben der die Funktionsweise repräsentiert
    int const arrow_right_key = ...; // dito
    // ...
    

    ich möchte aber noch frei bewegende NPC's einfügen.

    Auch dann ist es überflüssig die gesamte Konsole neu zu Zeichnen; Belli hat es ja zusammengefasst.



  • Danke für die ganzen Antworten! 🙂
    Ich kann euch hier schon mal zeigen inwiefern ich den Quelltext schon verändert habe.
    Nun werde ich mich darum kümmern das:
    1. enum für die Map benutzen
    2. Nur die Positionen löschen, die sich ändern
    3. Den Quelltext für die Key's ein bisschen schöner gestalten (Auch wenn ich das noch nicht ganz verstanden habe^^)

    #include <iostream>
    #include <Windows.h>
    #include <conio.h>
    #include <stdio.h>
    
    using namespace std;
    
    void gotoxy(int x, int y)         //funktion gotoxy
    {          
      COORD c;
    
      c.X = x;
      c.Y = y;
      SetConsoleCursorPosition (GetStdHandle(STD_OUTPUT_HANDLE), c);
    }
    
    int main ()
    {
    	// Map initialization
    	//
    	enum Map_Objects
    	{
    		Nothing,
    		Player,
    		Wall,
    		Point
    	};
        const int x_Map = 40;
        const int y_Map = 20;
        Map_Objects Map[x_Map][y_Map];
    	//
    	////
    
    	// Variables initialization
    	//
        int Points = 0;
        int Key = 0;
        int x_Player = 0;
        int y_Player = 0;
        //
        ////
    
        // Reset MAP. MAP = 0 (Nothing " ")
        for (int y = 0; y < y_Map; y++)
        {
            for (int x = 0; x < x_Map; x++)
            {
                Map[x][y] = Nothing; 
            }   
        }
    
        // Initialization of all objects
        //
        // MAP: Player
        Map[0][0] = Player;
    
        // Map: Wall
        Map[1][0] = Wall; Map[1][1] = Wall; Map[0][3] = Wall; Map[1][3] = Wall; Map[2][3] = Wall;
    	Map[3][3] = Wall; Map[3][2] = Wall; Map[3][1] = Wall; Map[5][0] = Wall; Map[5][1] = Wall;
    	Map[5][2] = Wall; Map[5][3] = Wall;
    
        // MAP: Point
        Map[10][10] = Point;
        //
        ////
    
        // Game-Loop (MAP-render of all Map-parts & Control)
        while(true)
        {
    		// Map-Render (includes all objects)  
    		//    
            cout << "==========================================\n";
    
            for (int y = 0; y < y_Map; y++)
            {
                cout << "|";
                for (int x = 0; x < x_Map; x++)
                {
                    if (Map[x][y] == Player)
                        cout << "o";
                    else if (Map[x][y] == Wall)
                        cout << "X";
                    else if (Map[x][y] == Point)
                        cout << "P";
                    else
                        cout << " ";
                }
                cout << "|";
                cout << endl;  
            }
    
            cout << "==========================================\n";
            //
            ////
    
    		// Game-Stats   
    //x        cout << "Key ID: " << Key;
            cout << "     Points: " << Points;
    		cout << "     Coords: (" << x_Player << "|" << y_Player << ")    ";
    
        	// Reset all Player-Coordinates on Map
        	for (int y = 0; y < y_Map; y++)
            {
                for (int x = 0; x < x_Map; x++)
                {
                    if (Map[x][y] == Player)
                        Map[x][y] = Nothing;
                } 
            }
    
            // Reset Key-Input
            Key = 0;
    
            // Initialize new Key-Input if kbhit()
            if (kbhit())
                Key = getch();
    
    		// Check, Calculate & Set - The Key-Input
    		//  
            if (Key == 119 && Map[x_Player][y_Player-1] != Wall) // Key-Input: w
            {
                if (Map[x_Player][y_Player-1] == Point)
                    Points += 1;
                y_Player--;
            }
            else if (Key == 97 && Map[x_Player-1][y_Player] != Wall) // Key-Input: a
            {
                if (Map[x_Player-1][y_Player] == Point)
                    Points += 1;
                x_Player--;
            }
            else if (Key == 115 && Map[x_Player][y_Player+1] != Wall) // Key-Input: s
            {
                if (Map[x_Player][y_Player+1] == Point)
                    Points += 1;
                y_Player++;
            }
            else if (Key == 100 && Map[x_Player+1][y_Player] != Wall) // Key-Input: d
            {
                if (Map[x_Player+1][y_Player] == Point)
                    Points += 1;
                x_Player++;
            }
            //
            ////
    
    		// Initialize new Player-Coordinates on Map
            Map[x_Player][y_Player] = Player;
    
            // Reset Game-Event
    //x        Sleep(50);
            gotoxy(0, 0);
        } 
    }
    

Log in to reply