为什么getch()没有读取输入的最后一个字符?

时间:2017-05-16 12:02:54

标签: c buffer ncurses curses getch

我正在使用ncurses库在C中编写snake游戏,其中屏幕每秒更新一次。当玩游戏的人知道,如果用户输入各种键或长按键,则不应存储“缓冲”按键。换句话说,如果我按住w(向上键)并且stdin收到20 w s的序列,然后输入d(右键) ,我希望蛇立即向右移动,并忽略缓冲的w

我正在尝试使用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()似乎没有帮助。

3 个答案:

答案 0 :(得分:3)

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

即使在蛇游戏中,你也想要输入所有的角色。想象一下,蛇向右移动,玩家希望通过按向下进行“掉头”,然后立即离开。如果你的游戏只能拿起最后一个角色,蛇就会直接向左移动,从而自杀。相当令人沮丧的游戏体验;)

解决问题的方法很简单:让游戏循环轮询输入比蛇移动更频繁,并维护一个请求方向更改的队列。我已经在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();

以下是蛇实现和使用队列的方式:

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;
    }
    // [...]
}

祝你好运!

答案 1 :(得分:0)

我做了更多研究,发现:

  

flushinp()例程抛弃了用户输入的任何类型的未完成程序,但尚未被程序读取。

这解决了我的问题。

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

答案 2 :(得分:0)

以下建议的代码显示了如何忽略输入的最后一个键以外的所有键。

#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;
}