Tastenprellen?



  • Windows 10, das Programm ist mit Visual Studio Community geschrieben.

    Der Code ist sehr umfangreich, weswegen ich ihn gerne auf die wichtigsten Elemente kürzen möchte. Weiß aber nicht welche Teile wichtig für eine Analyse wären.

    Das wäre der Navigations-Teil, eventuell erkennt man ja bereits meine "Logik" x.X

                        // Opening map
                        system("cls");
                        std::cout << "* World map *\n\nMove with W-A-S-D.\n";
                        std::cout << "You are here: ";
                        maptile();
    
                        // Game Loop
                        while (choiceMap) {
                            std::cout << "\nWhich direction do you want to go?\n";
                            char ctrl = ' ';
                            std::cin >> ctrl;
    
                            // W-A-S-D control
                            if (ctrl == 'w') {
                                system("cls");
                                //   std::cout << "north\n";
                            }
                            else if (ctrl == 'a') {
                                system("cls");
                                //    std::cout << "west\n";
                            }
                            else if (ctrl == 's') {
                                system("cls");
                                //    std::cout << "south\n";
                            }
                            else if (ctrl == 'd') {
                                system("cls");
                                //    std::cout << "east\n";
                            }
                            else if (ctrl == 'b') {
                                system("cls");
                                std::cout << "* Backpack *\n\n";
                                std::cout << "Gold: " << gold << "\n";
                                if (key == true) {
                                    std::cout << "Key:  " << key << "\n";
                                }
                                else {
                                    std::cout << "You have no items.\n";
                                }
    
                            }
                            else if (ctrl == 'p') {
                                system("cls");
                                std::cout << "* Profile *\n\n";
                                std::cout << "Name:  " << name << "\n";
                                std::cout << "Level: " << lvl << "\n";
                                std::cout << "Exp.:  " << xp << "\n\n";
                                std::cout << "Str:   " << str << "\n";
                                std::cout << "Dex:   " << dex << "\n";
                                std::cout << "Vit:   " << vit << "\n";
                                std::cout << "Life:  " << lifeCurrent << "\n";
                            }
                            else if (ctrl == 'x') {
                                system("cls");
                                choiceMap = false;
                                location = 101;
                                //    std::cout << "Exit Map.\n";
                            }
                            else {
                                system("cls");
                                std::cerr << "Wrong input, please try again.\n\n";
                            }
    
                            // Navigation on the map
                            if ((location == 101) && (ctrl == 'w')) {
                                std::cout << "You can't move there.\n";
                                ctrl = ' ';
                            }
                            if ((location == 101) && (ctrl == 'a')) {
                                std::cout << "You can't move there.\n";
                                ctrl = ' ';
                            }
                            if ((location == 101) && (ctrl == 's')) {
                                location = 201;
                                ctrl = ' ';
                                maptile();
                            }
                            if ((location == 101) && (ctrl == 'd')) {
                                location = 102;
                                ctrl = ' ';
                                maptile();
                            }
    


  • Zustandsautomat:

    2 Zustände: KeyIsDown, KeyIsUp
    Initialzustand ist KeyIsUp
    2 Ereignisse: Keydown, Keyup

    Transitionen:
    Kommt Keydown in Zustand KeyIsUp, mach, was passieren soll und nächster Zustand ist KeyIsDown
    Kommt Keyup in Zustand KeyIsDown, mach, was passieren soll und nächster Zustand ist KeyIsUp

    Keyup wird ignoriert in Zustand KeyIsUp
    Keydown wird ignoriert in Zustand KeyIsDown



  • Die Frage ist auch, wie sich dein Programm verhalten soll. Sollen bei gedrückter Taste die Eingabe wiederholt werden, oder soll jede Taste erst "losgelassen" werden und muss erneut gedrückt werden, um eine Aktion auszulösen?

    Das von dir beschriebene Verhalten kommt vermutlich daher, dass das OS schneller in den Eingabepuffer schreibt, als dein Programm sie auslesen und behandeln kann. Als Quick & Dirty Lösung kannst mal versuchen, alle Zeichen im Puffer zu löschen, bevor du das nächste Zeichen liest.
    Füge zwischen Zeile 9 und 10 mal folgende Zeile ein:

    std::cin.ignore( std::numeric_limits<std::streamsize>::max() );
    

    Eventuell musst du zusätzlich die beiden Header <limits> und <ios>inkludieren.



  • @cpp-n00b sagte in Tastenprellen?:

    std::cin >> ctrl;

    Das Problem ist ja, dass cin erst liest, wenn du Enter drückst - und dann alle Zeichen direkt nacheinander kommen. Das ist für ein Spiel also wohl keine gute Wahl. Auch das system("cls") ist nicht wirklich toll.

    Was tun stattdessen?

    Da es ja um eine Consolenapp unter Windows geht, da mal nachschauen:

    1. Tasten lesen: https://learn.microsoft.com/en-us/windows/console/readconsoleinput?redirectedfrom=MSDN
    2. Bildschirm löschen: https://learn.microsoft.com/en-us/windows/console/clearing-the-screen

    Vielleicht kannst du mal in die Richtung weitergucken.



  • @DocShoe

    Mit copy & paste in die besagte Stelle eingefügt und limits und ios nach einem Test inkludiert.
    Der Befehl wird nicht mehr ausgeführt. Das Programm bleibt "stehen". Dasselbe Verhalten wenn ich die Includes einfüge. Noch eine Idee? Ich mag es Quick & Dirty...

    @wob

    Danke für den Tipp, das werde ich mir anschließend mal durchlesen und schauen ob das besagtes Problem löst.



  • ich hab hier n paar uralte Schnipsel bei mir gefunden:

    void ClearScreen(void)
    {
        CONSOLE_SCREEN_BUFFER_INFO csbi;
        COORD target = {0, 0};
        DWORD written;
    
        GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
        FillConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), ' ',
                                                csbi.dwSize.X * csbi.dwSize.Y,
                                                target, &written);
        FillConsoleOutputAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7,
                                                csbi.dwSize.X * csbi.dwSize.Y,
                                                target, &written);
    }
    
    struct taste
    {
       taste(int c, int k) : AsciiChar(c), VirtualKey(k){};
       int AsciiChar;
       int VirtualKey;
    };
    
    taste getInput()
    {
        INPUT_RECORD ir;
       
        DWORD dummy;
        do
        {
            ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &ir, 1, &dummy);
        }while(ir.EventType != KEY_EVENT || !ir.Event.KeyEvent.bKeyDown);
       
        return taste(ir.Event.KeyEvent.uChar.AsciiChar, ir.Event.KeyEvent.wVirtualKeyCode);
    }
    
    void consumeInput()
    {
        INPUT_RECORD ir;
       
        DWORD dummy = 1;
        
        while(dummy)
        {
            PeekConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &ir, 1, &dummy);
            if(dummy)
               ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &ir, 1, &dummy);
        }
    }
    

    ClearScreen leert den Bildschirm ohne das hässliche system(...)
    GetInput liest eine gedrückte Taste, ohne dass 'Enter' gedrückt werden muss
    consumeInput leert den Tastaturpuffer

    Vielleicht kannst Du was damit anfangen ...

    windows.h muss dafür eingebunden werden.



  • @cpp-n00b sagte in Tastenprellen?:

    @DocShoe

    Mit copy & paste in die besagte Stelle eingefügt und limits und ios nach einem Test inkludiert.
    Der Befehl wird nicht mehr ausgeführt. Das Programm bleibt "stehen". Dasselbe Verhalten wenn ich die Includes einfüge. Noch eine Idee? Ich mag es Quick & Dirty...

    @wob

    Danke für den Tipp, das werde ich mir anschließend mal durchlesen und schauen ob das besagtes Problem löst.

    Ja, war auch ein total bescheuerter Vorschlag von mir. Aus irgendwelchen Gründen bin ich davon ausgegangen, dass

    1. std::cin.ignore( std::numeric_limits<std::streamsize>::max() );den Eingabepuffer leert. Tut`s aber nicht.
    2. std::cin >> ctrl; ein einzelnes Zeichen liest und sofort zurückkehrt (also nicht auf Enter wartet)

    MIt deinem std::cinAnsatz wird das eher schwierig, ist es wirklich beabsichtigt, dass jede Eingabe mit Enter bestätigt werden soll?
    Nächster Quick&Dirty Vorschlag (dieses mal getestet ;)):

    char ch;
    std::cin >> ch; // Liest das erste Zeichen aus dem Eingabepuffer
    std::cin.clear(); // löscht die übrigen Zeichen aus dem Eingabepuffer
    

    Damit wird nur das erste Zeichen der Eingabe behandelt. Der Benutzer muss weiterhin die Eingabe mit Enter bestätigen, was dazu führen kann, dass sowas wie "wabc77uz0ä?" im Eingabepuffer steht, aber nur das erste Zeichen behandelt wird.
    Leider gibt es für dein Problem keine Standardlösung in C++, möglicherweise unterstützt dein Compiler die Funktionen getch() und kbdhit(), unter Umständen mit einem vorangestellten Underscore. Das sind aber keine Funktionen, die im C oder C++ Standard definiert sind.

    char ch = getch();
    

    Oder du benutzt, wie @wob schon vorgeschlagen hat, plattformspezifische Funktionen.

    Noch ein genereller Verbesserungshinweis:
    Trenne Funktionalitäten. Das Einlesen und Behandeln von Eingaben in einer Funktion ist unschön, das könnte man besser so umsetzen:

    void run_game_loop()
    {
       while( !terminated() )
       {
          char const ch = read_console_input();
          process_console_input( ch );
       }
    }
    

    PS:
    Üblicherweise möchte man nicht auf eine Benutzereingabe warten, sondern arbeitet mit einer ständig laufenden Schleife, die auch ohne Benutzereingabe weiterläuft, um div. andere Dinge erledigen zu können (NPCs tun irgendwas, die Welt verändert sich, Zeit läuft weiter, etc.) Das Ganze läuft dann vermutlich auch nicht einfach mehr unkontrolliert in einer Schleife, sondern ein Schleifendurchlauf wird in einem bestimmten, festen Intervall ausgeführt, z.b. alle 100ms. Für alle Ereignisse gibt es dann einen Countdown, bei dessen Ablauf dieses Ereignis behandelt wird und der Countdown zurückgesetzt wird.



  • @cpp-n00b sagte in Tastenprellen?:

    auf Konsole-Basis

    ... pdcurses ist hier vllt. einen Blick wert?



  • @DocShoe sagte in Tastenprellen?:

    Die Frage ist auch, wie sich dein Programm verhalten soll. Sollen bei gedrückter Taste die Eingabe wiederholt werden, oder soll jede Taste erst "losgelassen" werden und muss erneut gedrückt werden, um eine Aktion auszulösen?

    MIt deinem std::cinAnsatz wird das eher schwierig, ist es wirklich beabsichtigt, dass jede Eingabe mit Enter bestätigt werden soll?

    Mit aktuellem Wissen ist die Steuerung "leider" so, dass jeder Befehl einzeln eingegeben werden soll und dann mit Enter ausgeführt wird.
    Optimal ist das nicht, ich weiß...

    std::cin.clear(); // löscht die übrigen Zeichen aus dem Eingabepuffer

    HA! Love it! Danke, das scheint tatsächlich das "Prellen" bzw. den überlaufenden Eingabepuffer zu verhindern.

    Ich werde mir aber dennoch mal die anderen Vorschläge anschauen und wollte mich dafür auch natürlich bedanken, da ich mich jetzt dem nicht verschließen werde.

    Ich weiß auch, dass das "Teil-Programm" wahrscheinlich für euch alle wie Kraut und Rüben erscheint (Was würdet Ihr davonrennen wenn ihr das ganze seht 🙂 ). Das stimmt natürlich auch, aber ich versuche mich eben mit dem Wissensstand den ich besitze das Spiel hinzubekommen und dann peu à peu mit hoffentlich wachsenden Kenntnissen das Spiel zu verbessern und zu optimieren. Hab noch sooooo viele Ideen...

    Mir ist nämlich aufgefallen, während ich mich mit dem Programmieren beschäftigte: "Was soll ich programmieren?" Diese Frage scheint oft in den Foren aufzutauchen und ich habe auch Programme wie Taschenrechner, Hangman, etc durch, aber danach ging es wieder von vorne los.
    Und ein Rollenspiel scheint mir einfach ein neverending Journey zu sein, in dem ich noch so viele verschiedene Techniken implementieren kann. Deswegen ist das mein "Bushido".



  • @cpp-n00b sagte in Tastenprellen?:

    ...
    Ich weiß auch, dass das "Teil-Programm" wahrscheinlich für euch alle wie Kraut und Rüben erscheint (Was würdet Ihr davonrennen wenn ihr das ganze seht 🙂 ). Das stimmt natürlich auch, aber ich versuche mich eben mit dem Wissensstand den ich besitze das Spiel hinzubekommen und dann peu à peu mit hoffentlich wachsenden Kenntnissen das Spiel zu verbessern und zu optimieren. Hab noch sooooo viele Ideen...
    ...

    Gerade dann ist es sinnvoll über eine Restrukturierung nachzudenken. Wenn abzusehen ist, dass da noch viel mehr kommt, dann zahlt sich die Mehrarbeit jetzt aus, ansonsten läufst du Gefahr, dich in Sackgassen zu verrennen und den Überblick zu verlieren. Fang lieber früher an aufzuräumen, statt später.


Anmelden zum Antworten