PHP CLI - 获取用户输入,同时仍在后台执行操作


PHP CLI - get user input while still doing things in background

我正在开发一个游戏,用PHP编写,在控制台中运行。回想一下旧的MUD和其他基于文本的游戏,甚至是一些ASCII艺术!

无论如何,我试图做的是让事情发生,同时也接受用户输入。

例如,假设这是一个双人游戏,玩家 1 正在等待玩家 2 采取行动。只需侦听消息即可轻松完成此操作。

但是,如果玩家 1 想要更改某些选项怎么办?如果他们想要查看有关游戏状态方面的详细信息,该怎么办?丢掉比赛呢?玩家在等待对手采取行动时可能想做很多事情。

不幸的是,我现在拥有的最好的是Ctrl + C完全杀死了该程序。然后,另一个玩家保持挂起状态,直到连接断开。哦,游戏完全输了。

我通过fgets(STDIN)获得用户输入.但是这会阻止执行,直到收到输入(这通常是一件好事)。

像这样的控制台程序是否有可能同时处理输入和输出?还是我应该只看其他界面?

简而言之,PHP 不是为此而构建的,但您可能会从这些扩展之一中获得一些帮助。我不确定它们有多彻底,但您真的可能想使用文本 UI 库。(实际上你可能不想为此使用 PHP。

综上所述,您需要逐个字符从STDIN获取非阻塞输入。不幸的是,从 PHP 的角度来看,大多数终端都是缓冲的,所以在按下 Enter 之前你不会得到任何东西。

如果您在终端上运行stty -icanon(或操作系统的等效程序)以禁用缓冲,那么以下短程序基本上可以工作:

<?php
stream_set_blocking(STDIN, false);
$line = '';
$time = microtime(true);
$prompt = '> ';
echo $prompt;
while (true)
{
  if (microtime(true) - $time > 5)
  {
    echo "'nTick...'n$prompt$line";
    $time = microtime(true);
  }
  $c = fgetc(STDIN);
  if ($c !== false)
  {
    if ($c != "'n")
      $line .= $c;
    else
    {
      if ($line == 'exit' || $line == 'quit')
        break;
      else if ($line == 'help')
        echo "Type exit'n";
      else
        echo "Unrecognized command.'n";
      echo $prompt;
      $line = '';
    }
  }
}

(它依赖于启用本地回显来打印键入字符。

如您所见,我们只是永远循环往复。如果存在某个字符,请将其添加到$line 中。如果按回车键,则进程$line。同时,我们每五秒滴答一次,只是为了表明我们在等待输入时可以做其他事情。(这将消耗最大的CPU;您必须发出sleep()才能解决此问题。

这本身并不是一个实际的例子,但也许会让你朝着正确的方向思考。

可以使用

ncurses(非阻塞模式)和libevent来构建您描述的游戏。这样,您几乎不会消耗 CPU。处理单个键有时很尴尬(自己实现退格键,一点也不好玩 - 你知道各种操作系统在退格键上发送不同的键码吗?),如果你想正确支持 UTF-8,就会变得非常棘手。不过,完全可行。

特别是,通过读取网络和键盘(stdin)输入来广泛使用libevent是有益的。此函数使您能够侦听单个键:http://www.php.net/manual/en/function.ncurses-cbreak.php您可以稍后使用 libevent API 读取。要记住的关键是,您有时最终会一次读取多个密钥,并且必须对其进行处理(因此循环访问您已读取的所有内容)。否则,用户会很恼火地看到并非所有按键都"到达"应用程序,有些按键丢失。

对不起,马修,我将不得不不接受你的答案,因为我自己找到了:

使用以下代码接收用户输入,同时仍执行其他操作:

while(/* some condition that the code running is waiting on */) {
    // perform one step or iteration of that code
    exec("choice /N /C ___ /D _ /T _",$out,$ret);
    // /C is a list of letters that do something
    // /D is the default action that will be used as a no-op
    // /T is the amount of time to wait, probably best set to one second
    switch($ret) {
        // handle cases - the "default" case should be "continue 2"
    }
}

然后,这可以用来中断循环并进入选项菜单,或触发一些其他事件,或者如果使用得当,甚至可以用于键入命令。