3

I'm trying to do a simple game (Pong) in C++. The game is a "Console-Game". I've just write part of the code but now I found a trouble: I create a _getch() and _kbhit() function

    int _getch( ) {
        struct termios oldt, newt;
        int ch;
        tcgetattr( STDIN_FILENO, &oldt );
        newt = oldt;
        newt.c_lflag &= ~( ICANON | ECHO );
        tcsetattr( STDIN_FILENO, TCSANOW, &newt );
        ch = getchar();
        tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
        return ch;
}

int _kbhit() {
        static const int STDIN = 0;
        static bool initialized = false;
        if (! initialized) {
                termios term;
                tcgetattr(STDIN, &term);
                term.c_lflag &= ~ICANON;
                tcsetattr(STDIN, TCSANOW, &term);
                setbuf(stdin, NULL);
                initialized = true;
        }
        int bytesWaiting;
        ioctl(STDIN, FIONREAD, &bytesWaiting);
        return bytesWaiting;
}

but when I press a key, that is printed on console. How I can prevent this?

My full code:

#include <iostream> 
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <sys/select.h>
#include <stropts.h>
#include <sys/ioctl.h>

using namespace std;

void gotoxy(int x,int y){
       printf("\x1b[%d;%df",y,x);
}

int _getch( ) {
        struct termios oldt, newt;
    int ch;
    tcgetattr( STDIN_FILENO, &oldt );
    newt = oldt;
    newt.c_lflag &= ~( ICANON | ECHO );
    tcsetattr( STDIN_FILENO, TCSANOW, &newt );
    ch = getchar();
    tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
    return ch;
}

int _kbhit() {
    static const int STDIN = 0;
    static bool initialized = false;
    if (! initialized) {
        // Use termios to turn off line buffering
        termios term;
        tcgetattr(STDIN, &term);
        term.c_lflag &= ~ICANON;
        tcsetattr(STDIN, TCSANOW, &term);
        setbuf(stdin, NULL);
        initialized = true;
    }
    int bytesWaiting;
    ioctl(STDIN, FIONREAD, &bytesWaiting);
    return bytesWaiting;
}

class Ball{
    public:

};

class Game{
    public:
        void __init(){
            this->pointL = 0;
            this->pointR = 0;
        }

        void addScore(char side){
        if(side == 'l') pointL++;
        else if(side == 'r') pointR++;
        else cout<< "ERROR-2";
            return;
        }

        unsigned int getScore(char side){
            if(side == 'l') return this->pointL;
            else if (side == 'r') return this->pointR;
            else return 0;
        }

        bool isPlaying(){
            return this->playing;
        }

        bool stop(){
            this->playing = false;
            return true;
        }   
    private:
        unsigned int pointL, pointR;
        bool playing;
};

class Player{
    public:
        int pos[5][2];
        int maxX, maxY;
        void __init(int maxX, int maxY, char side){
            //create matrix with all cords of block of wall (r and l)
            this->maxX = maxX;
            this->maxY = maxY;
            if(side == 'l')
                for(int i = 0; i<5; i++){
                    pos[i][0] = 2;
                    pos[i][1] = 2+i;
                    gotoxy(pos[i][0],pos[i][1]);
                    cout<< "|";
                }
            else if(side == 'r')
                for(int i = 0; i<5; i++){
                    pos[i][0] = maxX-4;
                    pos[i][1] = 2+i;
                    gotoxy(pos[i][0],pos[i][1]);
                    cout<< "|";
                }
            else
                cout<<"ERRORE-1";

        }

        void moveUp(){
            gotoxy(pos[4][0],pos[4][1]);
            cout<< " ";
            for(int i = 0; i<5; i++){
                pos[i][1] = (pos[i][1] == 0)?pos[i][1]:pos[i][1]-1;
                gotoxy(pos[i][0],pos[i][1]);
                cout<< "|"; //solid rectangle
            }
        }

        void moveDown(){
            gotoxy(pos[4][0],pos[4][1]);
            cout<< " ";
            for(int i = 0; i<5; i++){
                pos[i][1] = (pos[i][1] == this->maxY)?pos[i][1]:pos[i][1]+1;
                gotoxy(pos[i][0],pos[i][1]);
                cout<< "|"; //solid rectangle
            }
        }
};

int main(){
    int a;
    Game game;
    Player pl1,pl2;
    cout<< "Ridimensiona la finestra come meglio preferisci, quando hai fatto, premi un tasto per continuare";
    _getch();
    struct winsize size;
    ioctl( 0, TIOCSWINSZ, (char *) &size );
    printf("\e[2J\e[H");
    pl1.__init(size.ws_row, size.ws_col, 'l');
    pl2.__init(size.ws_row, size.ws_col, 'r');
    //asap menu here
    cout<< "TEST: " << size.ws_row;
    char key;
    while(game.isPlaying()){
        if(_kbhit()){
            key = _getch();
            switch(key){ //when i press that keys, it's printed on terminal, how prevent?
                case 'w':
                    pl1.moveUp();
                    break;
                case 's':
                    pl2.moveDown();
                    break;
                case 'q':
                    game.stop();
                    break;
            }   
        }
    }
    return 0;
}
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
LucaLumetti
  • 339
  • 4
  • 15
  • 1
    Can you please create a [Minimal, Complete, and Verifiable Example](http://stackoverflow.com/help/mcve) to show how these functions are called? Especially in order they are called. – Some programmer dude Apr 30 '15 at 16:52
  • Looks as if setting the terminal to non-canonical isn't sufficient (it disables line editing but may not prevent echo). Try cfmakeraw(), cf. http://man7.org/linux/man-pages/man3/tcsetattr.3.html. I also seem to remember that some terminals/emulators have a "local echo" option which will just display the character related to key press without any host intervention. Make sure to disable that, too. – Peter - Reinstate Monica Apr 30 '15 at 16:56
  • The best option may be to access the keyboard device directly, instead of doing manipulations att the terminal level. – πάντα ῥεῖ Apr 30 '15 at 17:03
  • You're using [reserved identifiers](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier). – chris Apr 30 '15 at 17:06
  • Hmmmm... the edited example is minmal only with a grain of salt ;-). – Peter - Reinstate Monica Apr 30 '15 at 17:08
  • 4
    By the way, you should look into e.g. [ncurses](http://en.wikipedia.org/wiki/Ncurses) (or some other curses variant. – Some programmer dude Apr 30 '15 at 17:35

2 Answers2

3

Here is a program which does not echo keystrokes but writes the characters to stderr instead. If you compile it, say, with g++ -o t t.cpp then you can start it with e.g. ./t 2>somefile, thus redirecting stderr to somefile.

In a second terminal you can do cat somefile after each keystroke in order to control what the program writes. (Warning: less somefile didn't work on my cygwin installation because the file growth appeared only in chunks in less (I told it to wait for input by pressing "F")).

Since all key strokes are forwarded to the controlling program Control-C or other means to send a signal to the running program via its controlling terminal doesn't work. You have to kill it from a different terminal.

The basic idea is that read() blocks in raw mode until input is available, so there is no need for a separate kbhit().

#include <stdio.h>
#include <termios.h>
#include <unistd.h>

using namespace std;

int main()
{
    struct termios oldt;
    if( tcgetattr( 0, &oldt ) ) 
    { 
        fprintf(stderr, "Error getting term attribs\n"); 
    }
    cfmakeraw(&oldt);

    if( tcsetattr(0, TCSANOW, &oldt) ) 
    {
        fprintf(stderr, "Error setting term attribs\n");
    }

    char inp;
    while(true)
    {
        int bytesRead = read(0, &inp, 1);

        if( bytesRead <= 0)
        {
            fprintf(stderr, "oops, bytes read return val is %d\n", bytesRead);
        }
        else
        {
            write(2, &inp, 1);
        }
    }
}
Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
  • Perhaps I should add that the program invoker is free to redirect standard input, in which case the program would completely ignore key strokes. That may be undesirable; for example reading a login password will always happen directly from the controlling terminal, independent of standard input. – Peter - Reinstate Monica Apr 30 '15 at 18:10
0

You may use:

#include <iostream>
#include <stdexcept>
#include <termios.h>
#include <unistd.h>

// A class for mofdifying the behavour of a terminal.
class Terminal
{
    // Types
    // =====

    public:
    typedef speed_t speed_type;

    // Terminal
    // ========

    public:
    // Initialize the terminal file descriptor and store the attributes of the terminal.
    Terminal(int fd)
    :   m_fd(fd), m_restore(get(fd))
    {}

    // Restore the orignal attributes of the terminal
    ~Terminal() {
        set(m_fd, m_restore, false);
    }

    Terminal(const Terminal&) = delete;
    const Terminal& operator = (const Terminal&) = delete;

    int fd() const { return m_fd; }
    void restore() { set(m_fd, m_restore); }

    protected:
    // Get attributes of a terminal
    static termios get(const int fd) {
        termios attributes;
        if(tcgetattr(fd, &attributes) < 0) {
            throw std::runtime_error("Terminal");
        }
        return attributes;
    }

    // Set attributes of a terminal
    static void set(const int fd, const termios& attributes, bool exception = true) {
        if(tcsetattr(fd, TCSANOW, &attributes) < 0 && exception) {
            throw std::runtime_error("Terminal");
        }
    }

    // Set attributes of a terminal
    static void set(const int fd, int action, const termios& attributes, bool exception = true) {
        if(tcsetattr(fd, action, &attributes) < 0 && exception) {
            throw std::runtime_error("Terminal");
        }
    }

    private:
    int m_fd;
    termios m_restore;
};

// A class for mofdifying the input behavour of a terminal.
class StdInputTerminal : public Terminal
{
    // Constants
    // =========

    public:
    enum Attributes {
        Blocking = 0x01,
        Echo = 0x02
    };

    // Static
    // ======

    public:
    // Clear available input in std::cin
    static void clear() {
        termios attributes = disable_attributes(Blocking);
        while(std::cin)
            std::cin.get();
        std::cin.clear();
        set(fileno(stdin), attributes);
    }

    // StdInputTerminal
    // ================

    public:
    // Initialize with 'stdin'
    StdInputTerminal()
    :   Terminal(fileno(stdin))
    {}

    public:
    // Disable attributes specified by any combination of Attributes flags
    void disable(unsigned flags) { disable_attributes(flags); }

    // Disable blocking
    void disable_blocking() { disable_attributes(Blocking); }

    protected:
    // Set attributes of the terminal
    static termios disable_attributes(unsigned flags) {
        const int fd = fileno(stdin);
        termios attributes = get(fd);
        termios a = attributes;
        if(flags & Blocking) {
            a.c_lflag &= ~ICANON;
            a.c_cc[VMIN] = 0;
            a.c_cc[VTIME] = 0;
        }
        if(flags & Echo) {
            a.c_lflag &= ~ECHO;
        }
        set(fd, a);
        return attributes;
    }
};


// Sample Usage
// ============

int kbhit() {
    StdInputTerminal terminal;
    terminal.disable(StdInputTerminal::Blocking | StdInputTerminal::Echo);
    int result = 0;
    char c;
    if( ! std::cin.get(c))
        std::cin.clear();
    else
    {
        result = c;
        std::cin.unget();
    }
    return result;
}

int getch() {
    StdInputTerminal terminal;
    terminal.disable(StdInputTerminal::Blocking | StdInputTerminal::Echo);
    char result = 0;
    while(true) {
        std::cin.get(result);
        if(std::cin.eof()) {
            std::cin.clear();
            usleep(100000);
        }
        else break;
    }
    if(std::cin.fail()) {
        std::cin.clear();
        result = 0;
    }
    return result;
}


// Test
// ====

int main()
{
    std::cout << "Enter a single sample character: ";
    char c;
    std::cin.get(c);
    std::cin.unget();

    if(kbhit()) {
        int ch = getch();
        std::cout << "Entered character: " << char(ch);
        if(kbhit()) {
            int ch = getch();
            std::cout << " Newline " << char(ch);
            if(kbhit()) {
                std::string line;
                getline(std::cin, line);
                std::cout << "\nToo many sample characters: " << line << '\n';
            }
        }
    }
}

(Please excuse the incomplete classes, thy are experimental)