PHP CLI - 在后台运行时获取用户输入

时间:2012-06-14 00:25:14

标签: php io command-line-interface

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

无论如何,我正在尝试做的事情是在接受用户输入的同时发生事情。

例如,让我们说这是一个双人游戏,而玩家1正在等待玩家2进行移动。只需收听消息即可轻松完成。

但是如果玩家1想要改变一些选择呢?如果他们想要查看有关游戏状态方面的详细信息怎么办?承认游戏怎么样?在等待对手进行移动时,玩家可能想做很多事情。

不幸的是,我现在最好的是Ctrl + C完全杀死程序。然后另一个玩家被挂起,直到连接断开。哦,游戏完全丢失了。

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

这样的控制台程序是否可以同时处理输入和输出?或者我应该只看一些其他界面?

3 个答案:

答案 0 :(得分:3)

简而言之,PHP不是为此而构建的,但您可以从these extensions之一获得一些帮助。我不确定它们有多彻底,但你真的可能想要使用文本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。与此同时,我们每5秒钟就会发出一声,只是为了表明我们在等待输入时可能会做其他事情。 (这将消耗最大的CPU;您必须发出sleep()来解决这个问题。)

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

答案 1 :(得分:0)

可以使用ncurses(非阻塞模式)和libevent构建一个类似于你描述的游戏。这样,您就可以接近没有CPU消耗。处理单个密钥有时很尴尬(自己实现Backspace,它根本不是很有趣 - 你知道各种操作系统在Backspace上发送不同的密码吗?),如果你想要正确支持UTF-8,那就非常棘手。仍然,完全可行。

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

答案 2 :(得分:0)

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

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

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"
    }
}

然后可以使用它来中断循环并进入选项菜单,或触发其他一些事件,或者如果使用正确,甚至可以用来输出命令。