为什么 getch() 不读取最后输入的字符?

Why doesn't getch() read the last character entered?

我正在使用 ncurses 库用 C 编写一个 snake 游戏,其中屏幕每秒更新一次。玩过游戏的人都知道,如果用户输入各种键或者长按某个键,应该不会有'buffered'次按键被存储。换句话说,如果我按住 w(向上键)并且 stdin 接收到一个 20 w 的序列,然后输入一个 d(右键) ,我希望蛇立即向右移动,而忽略缓冲的 ws.

我正在尝试使用 ncurses 函数 getch() 来实现此目的,但出于某种原因,我正在实现我刚才描述的不良效果;也就是说,在考虑最后按下的键之前,首先存储和处理任何长按键,正如这个 MWE 将说明的那样:

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

int main(){

    char c = 'a';

    initscr();
    cbreak();
    noecho();

    for(;;){
        fflush(stdin);
        timeout(500);
        c = getch();
        sleep(1);
        printw("%c", c);
    }

    return 0;
}

有什么方法可以修改此代码,以便 getch() 忽略任何缓冲文本?之前的 fflush() 似乎没有帮助。

首先回答您的直接问题:即使您一次阅读 stdin 一个字符,您通常也不想错过任何一个。如果 getch() 只是 return 最后输入的任何字符,使用 getch() 的输入例程将变得非常不可靠。

即使在贪吃蛇游戏中,您也确实希望输入所有字符。想象一下蛇向右移动,玩家想要通过击打 down 然后立即 left 来做一个 "u-turn"。如果您的游戏只拾取最后一个角色,蛇会直接向左移动并因此自杀。相当令人沮丧的游戏体验;)

您的问题的解决方案很简单:让您的游戏循环轮询输入比蛇移动更频繁,并维护请求的方向更改队列。我已经在 my curses-based snake game.

中做到了

这是游戏循环中的相关代码:

typedef enum
{
    NONE,
    LEFT,
    DOWN,
    RIGHT,
    UP
} Dir;

// [...]

ticker_start(10);
while (1)
{
    screen_refresh(screen);
    ticker_wait();
    key = getch();
    if (key == 'q' || key == 'Q')
    {
        // quit game
    }
    switch (key)
    {
        case KEY_LEFT:
            snake_setDir(snake, LEFT);
            break;

        case KEY_DOWN:
            snake_setDir(snake, DOWN);
            break;

        case KEY_UP:
            snake_setDir(snake, UP);
            break;

        case KEY_RIGHT:
            snake_setDir(snake, RIGHT);
            break;

        case ' ':
            // pause game
            break;
    }

    if (!--nextStep)
    {
        step = snake_step(snake); // move the snake
        // code to check for food, killing, ...
    }

    if (!--nextFood)
    {
        // add a new food item
    }

}
ticker_stop();

下面是 snake 是如何实现和使用队列的:

struct snake
{
    // queue of requested directions:
    Dir dir[4];

    // more properties
};

void
snake_setDir(Snake *self, Dir dir)
{
    int i;
    Dir p;

    p = self->dir[0];
    for (i = 1; i < 4; ++i)
    {
        if (self->dir[i] == NONE)
        {
            if (dir != p) self->dir[i] = dir;
            break;
        }
        p = self->dir[i];
    }
}

static void dequeueDir(Snake *self)
{
    int i;

    if (self->dir[1] != NONE)
    {
        for (i=0; i<3; ++i) self->dir[i] = self->dir[i+1];
        self->dir[3] = NONE;
    }
}

Step
snake_step(Snake *self)
{
    dequeueDir(self);
    // [...]

    switch (self->dir[0])
    {
        case LEFT:
            --newHead->x;
            break;
        case DOWN:
            ++newHead->y;
            break;
        case RIGHT:
            ++newHead->x;
            break;
        case UP:
            --newHead->y;
            break;
        default:
            break;
    }
    // [...]
}

祝你游戏顺利!

我做了更多研究并发现:

The flushinp() routine throws away any typeahead that has been typed by the user and has not yet been read by the program.

这解决了我的问题。

来源:https://linux.die.net/man/3/flushinp

下面建议的代码显示了如何忽略除最后输入的键之外的所有键。

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

int main( void )
{
    char c = ' ';
    char lastKey;

    initscr();
    cbreak();
    noecho();

    for(;;)
    {
        timeout(500);

        while( (c = getch()) != EOF )
        {
            lastKey = c;
        }

        sleep(1);

        if( EOF == c )
        {
            printw("%c", lastKey);
        }
    }

    return 0;
}